def test_spec(self): # Aggregate of a relation. node = L.pe('count(R)') spec = AggrSpec.from_node(node) self.assertEqual(spec.aggrop, 'count') self.assertEqual(spec.rel, 'R') self.assertEqual(spec.relmask, Mask('u')) self.assertEqual(spec.params, ()) self.assertEqual(spec.oper_demname, None) self.assertEqual(spec.oper_demparams, None) constrs = spec.get_domain_constraints('A') exp_constrs = [] self.assertEqual(constrs, exp_constrs) # Aggregate of a setmatch, with demand. node = L.pe('count(DEMQUERY(foo, [c1], ' 'setmatch(R, "bub", (c1, c2))))') spec = AggrSpec.from_node(node) self.assertEqual(spec.aggrop, 'count') self.assertEqual(spec.rel, 'R') self.assertEqual(spec.relmask, Mask('bub')) self.assertEqual(spec.params, ('c1', 'c2')) self.assertEqual(spec.oper_demname, 'foo') self.assertEqual(spec.oper_demparams, ('c1', )) constrs = spec.get_domain_constraints('A') exp_constrs = [('A.1', 'R.1'), ('A.2', 'R.3')] self.assertEqual(constrs, exp_constrs)
def test_trel_bindmatch(self): code = trel_bindmatch('_TUP2', Mask('bbu'), ['t', 'x', 'y'], L.pc('pass'), typecheck=True) exp_code = L.pc(''' if (isinstance(t, tuple) and (len(t) == 2)): if (t[1] == x): for y in setmatch({(t, t[0], t[1])}, 'bbu', (t, x)): pass ''') self.assertEqual(code, exp_code) code = trel_bindmatch('_TUP2', Mask('bbu'), ['t', 'x', 'y'], L.pc('pass'), typecheck=False) exp_code = L.pc(''' if (t[1] == x): for y in setmatch({(t, t[0], t[1])}, 'bbu', (t, x)): pass ''') self.assertEqual(code, exp_code) code = trel_bindmatch('_TUP2', Mask('ubw'), ['t', 'x', 'y'], L.pc('pass'), typecheck=True) exp_code = L.pc(''' for t in setmatch(_TUP2, 'ubw', x): pass ''') self.assertEqual(code, exp_code)
def from_AST(cls, node, factory): """Construct from an Enumerator node of form <vars> in <rel> Alternatively, the rhs may be a setmatch of a rel, where the mask is a lookupmask and the key is a vartuple. """ checktype(node, L.Enumerator) lhs = L.get_vartuple(node.target) rhs = node.iter if L.is_name(rhs): rel = L.get_name(rhs) elif isinstance(rhs, L.SetMatch) and L.is_vartuple(rhs.key): keyvars = L.get_vartuple(rhs.key) # Make sure we're dealing with a lookupmask and that the # key vars agree with the mask. mask = Mask(rhs.mask) assert mask.is_lookupmask assert mask.lookup_arity == len(keyvars) lhs = keyvars + lhs rel = L.get_name(rhs.target) else: raise TypeError return cls(lhs, rel)
def to_AST(self): mask = Mask.from_keylen(len(self.lhs) - 1) keyvars = self.lhs[:-1] var = self.lhs[-1] sm = L.SMLookup(L.ln(self.rel), mask.make_node().s, L.tuplify(keyvars), None) return L.Enumerator(L.sn(var), L.Set((sm, )))
def from_node(cls, node): checktype(node, L.Aggregate) if isinstance(node.value, L.DemQuery): assert all(isinstance(a, L.Name) for a in node.value.args) oper_demparams = tuple(a.id for a in node.value.args) oper_demname = node.value.demname oper = node.value.value else: oper_demparams = None oper_demname = None oper = node.value if isinstance(oper, L.Name): rel = oper.id relmask = Mask.U params = () elif (isinstance(oper, L.SetMatch) and isinstance(oper.target, L.Name) and L.is_vartuple(oper.key)): rel = oper.target.id relmask = Mask(oper.mask) params = L.get_vartuple(oper.key) else: raise L.ProgramError('Bad aggregate operand', node=node) return cls(node.op, rel, relmask, params, oper_demname, oper_demparams)
def handle_ms_smnsassignkey(self, f, target, maskstr: 'Str', key, elem, prefix: 'Str'): from incoq.compiler.set import Mask mask = Mask(maskstr) assert mask.is_keymask # vars for each bound component ((len(mask) - 1) many). vars = [prefix + str(i) for i in range(1, len(mask))] # var for element component evar = prefix + 'elem' return self.pc(''' S_VARS = KEY if not setmatch(TARGET, MASK, KEY).isempty(): S_EVAR = TARGET.smlookup(MASK, KEY) TARGET.remove(PARTS_OLD) TARGET.add(PARTS_NEW) ''', subst={ 'S_VARS': tuplify(vars, lval=True), 'KEY': key, 'TARGET': target, 'MASK': Str(maskstr), 'S_EVAR': evar, 'PARTS_OLD': tuplify(vars + [evar]), 'PARTS_NEW': tuplify(vars + [elem]) })
def to_AST(self): mask = Mask.from_keylen(len(self.lhs) - 1) keyvars = self.lhs[:-1] var = self.lhs[-1] sm = L.SMLookup(L.ln(self.rel), mask.make_node().s, L.tuplify(keyvars), None) return L.Enumerator(L.sn(var), L.Set((sm,)))
def get_used_filters(ds, ordering, use_tag_checks): """Take in the demand structures for a query, and an ordering of clauses for one of the query's maintenance comps. Return a set of the clause indices for which filter relations are used. """ # Ignore conditions. ordering = [(i, cl, bindenv) for i, cl, bindenv in ordering if cl.kind is Clause.KIND_ENUM] filters = ds.filters filters_by_index = {f.i: f for f in filters} used_indices = set() for i, cl, bindenv in ordering: if cl.kind is Clause.KIND_COND: continue if i not in filters_by_index: continue # Singleton clauses are filtered if we're doing tag checks. deltamask = Mask.from_vars(cl.enumlhs, cl.enumlhs) if cl.isdelta and (use_tag_checks or deltamask.has_wildcards): used_indices.add(i) # Consult clause rules. elif cl.needs_filtering(bindenv): used_indices.add(i) return used_indices
def rate(self, bindenv): mask = Mask.from_vars(self.lhs, bindenv) if mask.is_allunbound: return Rate.UNRUNNABLE elif mask == Mask.OUT: return Rate.CONSTANT return super().rate(bindenv)
def test_inc_relmatch(self): spec = AuxmapSpec('R', Mask('bu')) tree = L.p(''' R.add((1, 2)) print(setmatch(R, 'bu', 1)) ''') tree = inc_relmatch(tree, self.manager, spec) exp_tree = L.p(''' _m_R_out = Map() def _maint__m_R_out_add(_e): (v1_1, v1_2) = _e if (v1_1 not in _m_R_out): _m_R_out.assignkey(v1_1, set()) _m_R_out[v1_1].add(v1_2) def _maint__m_R_out_remove(_e): (v2_1, v2_2) = _e _m_R_out[v2_1].remove(v2_2) if _m_R_out[v2_1].isempty(): _m_R_out.delkey(v2_1) with MAINT(_m_R_out, 'after', 'R.add((1, 2))'): R.add((1, 2)) _maint__m_R_out_add((1, 2)) print(_m_R_out.imglookup(1)) ''') self.assertEqual(tree, exp_tree)
def test_queryfinder(self): code = L.p(''' print(setmatch(R, 'bu', a)) print(setmatch(R, 'ub', a)) print(setmatch(R, 'bu', b)) print(setmatch({(1, 2)}, 'bu', 1)) S.add((3, 4, 5)) print(S.smlookup('bbu', (3, 4))) ''') auxmap_specs = RelmatchQueryFinder.run(code) exp_specs = {AuxmapSpec('R', Mask('bu')), AuxmapSpec('R', Mask('ub')), AuxmapSpec('S', Mask('bbu'))} self.assertCountEqual(auxmap_specs, exp_specs)
def get_res_code(self): """Return code (expression) to lookup the result.""" params = self.inccomp.comp.params if len(params) > 0: resexp = self.inccomp.spec.resexp assert isinstance(resexp, L.Tuple) resexp_arity = len(resexp.elts) n_rescomponents = resexp_arity - len(params) maskstr = 'b' * len(params) + 'u' * n_rescomponents masknode = Mask(maskstr).make_node() paramsnode = L.tuplify(params) code = L.pe(''' setmatch(RES, MASK, PARAMS) ''', subst={ 'RES': L.ln(self.inccomp.name), 'MASK': masknode, 'PARAMS': paramsnode }) else: code = L.ln(self.inccomp.name) return code
def test_resexp_vars(self): resexp = L.pe('(a + b, (c, d), (a, c, e, f))') bounds, unbounds = split_resexp_vars(resexp, Mask('bbu')) exp_bounds = {'c', 'd'} exp_unbounds = {'a', 'e', 'f'} self.assertEqual(bounds, exp_bounds) self.assertEqual(unbounds, exp_unbounds)
def rate(self, bindenv): mask = Mask.from_vars(self.lhs, bindenv) if mask.is_allbound: return Rate.CONSTANT_MEMBERSHIP elif mask.is_allunbound: return Rate.NOTPREFERRED else: return Rate.NORMAL
def get_code(self, bindenv, body): mask = Mask.from_vars(self.lhs, bindenv) assert not mask.is_allunbound return trel_bindmatch(make_trel(self.arity), mask, self.lhs, body, typecheck=self.typecheck)
def rate(self, bindenv): mask = Mask.from_vars(self.lhs, bindenv) if mask.is_allunbound: return Rate.UNRUNNABLE elif (mask.parts[0] == 'b' and (mask.parts[1] == 'b' or mask.parts[1] == '1')): return Rate.CONSTANT return super().rate(bindenv)
def get_code(self, bindenv, body): mask = Mask.from_vars(self.lhs, bindenv) bvars, uvars, _eqs = mask.split_vars(self.lhs) return mset_bindmatch(mask, bvars, uvars, body, typecheck=self.typecheck)
def split_resexp_vars(resexp, mask): """Given a result expression of a comprehension and a mask over the comprehension result, determine which variables appearing in the result expression are bound and unbound. Return the set of bounds and unbounds respectively. A variable is considered bound if it occurs somewhere within any subexpression corresponding to a bound part of the mask, where this subexpression is injective. Otherwise it is unbound. For example, if the result expression is ((x, y), z) and the mask if 'bu', then x and y are bound and z is unbound. However, if the result expression were (x + y, z) then they would all be unbound. E.g., if the result expression is ((x, y), z) and the mask is 'bu', then x and y are bound. But if the result expression is (x + y, z), then no variables are bound. """ find = partial(L.VarsFinder.run, ignore_functions=True) boundvars = set() unboundvars = set() if isinstance(resexp, L.Tuple): boundexps, unboundexps, _ = mask.split_vars(resexp.elts) # Determine bound vars. for e in boundexps: # Injective expressions include simple variable names and # tuple trees of variable names. if L.is_injective(e): boundvars.update(find(e)) # Determine unbound vars. for e in unboundexps: unboundvars.update(find(e)) unboundvars.difference_update(boundvars) else: # Special case: If the result expression is not a tuple, # then the mask is either a single bound or single unbound. if mask == Mask('b'): boundvars = find(resexp) elif mask == Mask.U or mask == Mask('w'): unboundvars = find(resexp) else: assert () return boundvars, unboundvars
def rate(self, bindenv): # Require demand parameters to be bound. mask = Mask.from_vars(self.enumlhs, bindenv) bounds, _unbounds, _eqs = mask.split_vars(self.cl.lhs) if not set(bounds).issuperset(set(self.demparams)): return Rate.UNRUNNABLE return self.cl.rate(bindenv)
def mainttest_helper(self, maskstr): spec = AuxmapSpec('R', Mask(maskstr)) # Make the prefix '_' so it's easier to read/type. self.manager.namegen.next_prefix = lambda: '_' code = make_auxmap_maint_code(self.manager, spec, L.ln('e'), 'add') return code
def get_code(self, bindenv, body): deltamask = Mask.from_vars(self.lhs, self.lhs) mask = Mask.from_vars(self.lhs, bindenv) bvars, uvars, _eqs = mask.split_vars(self.lhs) if mask.has_wildcards: # Can this be streamlined into something more readable, # like expressing the deltamatch as an If-guard? val = L.DeltaMatch(L.ln(self.rel), deltamask.make_node().s, self.val, self.limit) return L.pc(''' for UVARS in setmatch(VAL, MASK, BVARS): BODY ''', subst={'VAL': val, 'MASK': mask.make_node(), 'BVARS': L.tuplify(bvars), 'UVARS': L.tuplify(uvars, lval=True), '<c>BODY': body}) else: return make_tuplematch(self.val, mask, bvars, uvars, body)
def visit_IndefImgsetCost(self, cost): rel = cost.rel inv_cost = None if rel in self.invs: spec = self.invs[rel].spec if isinstance(spec, CompSpec): boundvars, _ = split_resexp_vars(spec.resexp, cost.mask) info = get_nondet_info(spec, boundvars) memconstrs = spec.get_membership_constraints() inv_cost = self.assemble_nondet_cost(info, memconstrs) elif isinstance(spec, AggrSpec): # As above for NameCost, but exclude the parameters that # are bound. First determine what parameters are bound # by the mask in this cost. vars = list(spec.params) + [object()] bounds, _unbounds, _eqs = cost.mask.split_vars(vars) # Now modify the parameter projection mask to project # them away. mask = spec.relmask.make_param_proj_mask() assert (len(spec.params) == len( [True for p in mask.parts if p == 'u'])) params = iter(spec.params) new_parts = [] for part in mask.parts: if part == 'u': p = next(params) if p in bounds: new_parts.append('w') continue new_parts.append(part) new_mask = Mask(new_parts) inv_cost = IndefImgsetCost(spec.rel, new_mask) inv_cost = self.visit(inv_cost) else: assert () # Get the dompath for each unbound component and multiply # them together. If any domain is unknown, the whole cost # is left alone. dom_cost = None dompaths = self.dompaths_for_mask(rel, cost.mask) if dompaths is not None: factors = [self.dompath_to_size(s) for s in dompaths] # Check for None again since there could've been a # deeper nested dompath missing. if all(c is not None for c in factors): dom_cost = ProductCost(factors) cost = self.make_min((inv_cost, dom_cost)) return cost
def from_AST(cls, node, factory): """Construct from Enumerator node of form <vars> in deltamatch(<rel>, <mask>, <val>, <limit>) """ checktype(node, L.Enumerator) lhs = L.get_vartuple(node.target) checktype(node.iter, L.DeltaMatch) rel = L.get_name(node.iter.target) mask = Mask(node.iter.mask) val = node.iter.elem limit = node.iter.limit if limit not in [0, 1]: raise TypeError inferred_mask = Mask.from_vars(lhs, lhs) assert mask == inferred_mask return cls(lhs, rel, val, limit)
def from_AST(cls, node, factory): """Construct from an Enumerator node of form var in {<rel>.smlookup(<mask>, <key vars>)} """ checktype(node, L.Enumerator) var = L.get_name(node.target) sm = L.get_singletonset(node.iter) checktype(sm, L.SMLookup) rel = L.get_name(sm.target) mask = Mask(sm.mask) keyvars = L.get_vartuple(sm.key) # Ensure the mask is consistent with how it's used. if mask != Mask.from_keylen(len(keyvars)): raise TypeError lhs = keyvars + (var, ) return cls(lhs, rel)
def get_code(self, bindenv, body): deltamask = Mask.from_vars(self.lhs, self.lhs) mask = Mask.from_vars(self.lhs, bindenv) bvars, uvars, _eqs = mask.split_vars(self.lhs) if mask.has_wildcards: # Can this be streamlined into something more readable, # like expressing the deltamatch as an If-guard? val = L.DeltaMatch(L.ln(self.rel), deltamask.make_node().s, self.val, self.limit) return L.pc(''' for UVARS in setmatch(VAL, MASK, BVARS): BODY ''', subst={ 'VAL': val, 'MASK': mask.make_node(), 'BVARS': L.tuplify(bvars), 'UVARS': L.tuplify(uvars, lval=True), '<c>BODY': body }) else: return make_tuplematch(self.val, mask, bvars, uvars, body)
def test_mapset(self): code = mapset_bindmatch(Mask('bbb'), ['x', 'y', 'z'], [], L.pc('pass'), typecheck=True) exp_code = L.pc(''' if isinstance(x, Map): if y in x and x[y] == z: pass ''') self.assertEqual(code, exp_code) code = mapset_bindmatch(Mask('bbu'), ['x', 'y'], ['z'], L.pc('pass'), typecheck=False) exp_code = L.pc(''' if y in x: z = x[y] pass ''') self.assertEqual(code, exp_code) code = mapset_bindmatch(Mask('buu'), ['x'], ['y', 'z'], L.pc('pass'), typecheck=False) exp_code = L.pc(''' for y, z in x.items(): pass ''') self.assertEqual(code, exp_code) with self.assertRaises(AssertionError): mapset_bindmatch(Mask('uuu'), [], ['x', 'y', 'z'], L.pc('pass'), typecheck=True) code = mapset_bindmatch(Mask('ubb'), ['y', 'z'], ['x'], L.pc('pass'), typecheck=True) exp_code = L.pc(''' for x in setmatch(_MAP, 'ubb', (y, z)): pass ''') self.assertEqual(code, exp_code)
def expr_tosizecost(self, expr): """Turn an iterated expression into a cost bound for its cardinality. """ if isinstance(expr, L.Name): return NameCost(expr.id) # Catch case of iterating over a delta set. # We'll just say O(delta set), even though it can have # duplicates. elif (isinstance(expr, L.Call) and isinstance(expr.func, L.Attribute) and isinstance(expr.func.value, L.Name) and expr.func.attr == 'elements'): return NameCost(expr.func.value.id) elif isinstance(expr, L.SetMatch): if isinstance(expr.target, (L.Set, L.DeltaMatch)): return UnitCost() elif (isinstance(expr.target, L.Name) and L.is_vartuple(expr.key)): keys = L.get_vartuple(expr.key) if all(k in self.args for k in keys): return DefImgsetCost(expr.target.id, Mask(expr.mask), L.get_vartuple(expr.key)) else: return IndefImgsetCost(expr.target.id, Mask(expr.mask)) else: return self.WarnUnknownCost(expr) elif isinstance(expr, L.DeltaMatch): return UnitCost() elif isinstance(expr, (L.Set, L.List, L.Tuple, L.Dict)): return UnitCost() else: return self.WarnUnknownCost(expr)
def handle_ms_smassignkey(self, f, target, maskstr: 'Str', key, elem, prefix: 'Str'): from incoq.compiler.set import Mask mask = Mask(maskstr) assert mask.is_keymask # vars for each bound component ((len(mask) - 1) many). vars = [prefix + str(i) for i in range(1, len(mask))] return self.pc(''' S_VARS = KEY TARGET.add(PARTS) ''', subst={ 'S_VARS': tuplify(vars, lval=True), 'KEY': key, 'TARGET': target, 'PARTS': tuplify(vars + [elem]) })
def __init__(self, aggr, spec, name, demname, uset_lru, half_demand): self.params = params = tuple(spec.params) """Aggregate parameters (same as operand parameters). Also same as aggregate demand parameters. """ self.aggrmask = Mask.from_keylen(len(params)) """Aggregate result retrieval mask.""" self.oper_deltamask = spec.relmask.make_delta_mask() """Mask for doing delta test upon change to aggregate operand.""" assert not (spec.has_oper_demand and not self.has_demand), \ 'Can\'t have non-demand-driven aggregate over demand-driven ' \ 'operand' assert not (half_demand and not self.has_demand), \ 'Can\'t use half-demand strategy when not using demand at all'
def test_VarRewriter(self): ST, TT, OT = L.SetType, L.TupleType, L.ObjType t = ST(TT([OT('A'), OT('B'), TT([OT('C'), OT('D')])])) self.manager.vartypes['R'] = t cost = NameCost('R') cost = VarRewriter.run(cost, self.manager) exp_cost = ProductCost( [NameCost('A'), NameCost('B'), NameCost('C'), NameCost('D')]) self.assertEqual(cost, exp_cost) cost = IndefImgsetCost('R', Mask('buu')) cost = VarRewriter.run(cost, self.manager) exp_cost = ProductCost([NameCost('B'), NameCost('C'), NameCost('D')]) self.assertEqual(cost, exp_cost)
def test_dompaths_for_mask(self): dompaths = self.trans.dompaths_for_mask('R', Mask.IN) exp_dompaths = ('R.1',) self.assertEqual(dompaths, exp_dompaths) dompaths = self.trans.dompaths_for_mask('R', Mask.UU) exp_dompaths = ('R.1', 'R.2') self.assertEqual(dompaths, exp_dompaths) dompaths = self.trans.dompaths_for_mask('R.2', Mask.U) exp_dompaths = ('R.2',) self.assertEqual(dompaths, exp_dompaths) dompaths = self.trans.dompaths_for_mask('R.2', Mask('b')) exp_dompaths = () self.assertEqual(dompaths, exp_dompaths) dompaths = self.trans.dompaths_for_mask('S', Mask.IN) exp_dompaths = None self.assertEqual(dompaths, exp_dompaths)
def from_AST(cls, node, factory): """Construct from an Enumerator node of form var in {<rel>.smlookup(<mask>, <key vars>)} """ checktype(node, L.Enumerator) var = L.get_name(node.target) sm = L.get_singletonset(node.iter) checktype(sm, L.SMLookup) rel = L.get_name(sm.target) mask = Mask(sm.mask) keyvars = L.get_vartuple(sm.key) # Ensure the mask is consistent with how it's used. if mask != Mask.from_keylen(len(keyvars)): raise TypeError lhs = keyvars + (var,) return cls(lhs, rel)
def handle_ms_smreassignkey(self, f, target, maskstr: 'Str', key, elem, prefix: 'Str'): from incoq.compiler.set import Mask mask = Mask(maskstr) assert mask.is_keymask vars = [prefix + str(i) for i in range(1, len(mask))] evar = prefix + 'elem' return self.pc(''' S_VARS = KEY S_EVAR = TARGET.smlookup(MASK, KEY) TARGET.remove(OLD_PARTS) TARGET.add(NEW_PARTS) ''', subst={ 'S_VARS': tuplify(vars, lval=True), 'KEY': key, 'S_EVAR': sn(evar), 'TARGET': target, 'MASK': Str(maskstr), 'OLD_PARTS': tuplify(vars + [evar]), 'NEW_PARTS': tuplify(vars + [elem]) })
def get_nondet_info(spec, bound_vars): """Given a comprehension and some bound variables, return information to help put a bound on the size of the part of the comprehension result that matches the bound variables. Specifically, we look at the non-determined variables. Choose a join order of the comprehension's clauses starting with the bound variables. For each clause in order, append to the result a triple of (iterated relation, mask, non-determined variables) where the non-determined variables are a subset of unbound vars in the clause that, together with the bound vars, functionally determine the remaining unbound vars in the clause. All the unbound variables in a clause become bound for future clauses. The mask is the lookup pattern that goes from the bound vars and determined unbound vars to the non-determined unbound vars. This goes on until all variables appearing in the comprehension result expression are bound. For each entry in the result, the newly introduced non- determined vars introduce a cost factor, and the overall comprehension's cost is bounded by the product of these factors. Each factor can itself be bounded in two ways. First, it can be considered recursively as an image set lookup over the iterated relation. Second, we can take all the membership constraints on the non-determined variables and use domain bounds for them. Assembling/minimizing these bounds is the caller's responsibility. """ goal_vars = L.VarsFinder.run(spec.resexp, ignore_functions=True) bound_vars = set(bound_vars) result = [] ordering = spec.join.get_ordering(bound_vars) for _i, cl, _bindenv in ordering: # Skip the remaining clauses if we bound all the # variables we need to. if bound_vars.issuperset(goal_vars): break # Ignore condition clauses and clauses that are not over # a relation. if cl.enumrel is None: continue det_vars = set(cl.get_determined_vars(bound_vars)) nondet_vars = set(cl.enumvars) - bound_vars - det_vars # Special case for lower cost bounds: If we happen to be able # to span the goal vars by taking some but not all of the nondet # vars and no det vars, then do that instead. if (bound_vars | nondet_vars).issuperset(goal_vars): to_vars = goal_vars - bound_vars from_vars = bound_vars else: to_vars = nondet_vars from_vars = bound_vars wild_vars = set(cl.enumvars) - from_vars - to_vars mask = Mask.from_vars(cl.enumlhs, from_vars, wild_vars) result.append((cl.enumrel, mask, to_vars)) bound_vars.update(cl.enumvars) return result
def to_AST(self): mask = Mask.from_vars(self.lhs, self.lhs) return L.Enumerator(L.tuplify(self.lhs, lval=True), L.DeltaMatch(L.ln(self.rel), mask.make_node().s, self.val, self.limit))
def get_code(self, bindenv, body): mask = Mask.from_vars(self.lhs, bindenv) bvars, uvars, _eqs = mask.split_vars(self.lhs) return make_tuplematch(self.val, mask, bvars, uvars, body)
def fits_string(self, bindenv, s): mask = Mask.from_vars(self.lhs, bindenv) return s == AuxmapSpec(self.rel, mask).lookup_name
def rate(self, bindenv): mask = Mask.from_vars(self.lhs, bindenv) if mask.is_keymask: return Rate.CONSTANT return super().rate(bindenv)