def try_decide_less_weak(lhs, rhs): """Weak decision procedure returning True, False, or None.""" assert is_code(lhs), lhs assert is_code(rhs), rhs # Try simple cases. if lhs is BOT or lhs is rhs or rhs is TOP: return True if lhs is TOP and rhs is BOT: return False # Destructure JOIN. if is_join(lhs): return trool_all(try_decide_less_weak(i, rhs) for i in iter_join(lhs)) if is_join(rhs): # This requires we give up at unreduced terms. return trool_any(try_decide_less_weak(lhs, i) for i in iter_join(rhs)) # Destructure ABS. while is_abs(lhs) or is_abs(rhs): lhs = unabstract(lhs) rhs = unabstract(rhs) assert lhs is not rhs, lhs # Destructure APP. lhs_head, lhs_args = unapply(lhs) rhs_head, rhs_args = unapply(rhs) # Give up at unreduced terms. if is_abs(lhs_head) or is_abs(rhs_head): if len(lhs_args) == len(rhs_args): if try_decide_less_weak(lhs_head, rhs_head) is True: if all(try_decide_less_weak(i, j) is True for i, j in zip(lhs_args, rhs_args)): return True return None if lhs_args and not is_var(lhs_head): return None if rhs_args and not is_var(rhs_head): return None # Distinguish solvable terms. if is_var(lhs_head) and is_var(rhs_head): if lhs_head is not rhs_head or len(lhs_args) != len(rhs_args): return False return trool_all( try_decide_less_weak(i, j) for i, j in zip(lhs_args, rhs_args) ) # Distinguish quoted terms. if is_quote(lhs_head) and is_quote(rhs_head): return try_decide_equal(lhs_head[1], rhs_head[1]) # Anything else is incomparable. return False
def iter_equations(test_id, suites=None): assert isinstance(test_id, str), test_id for code, comment, message in iter_test_cases(test_id, suites): if is_app(code) and is_app(code[1]) and code[1][1] is EQUAL: lhs = code[1][2] rhs = code[2] if is_quote(lhs) and is_quote(rhs): lhs = link(compile_(lhs[1])) rhs = link(compile_(rhs[1])) example = lhs, rhs, message if comment and parse_xfail(comment, test_id): example = pytest.mark.xfail(example) yield example
def abstract(body): """Abstract one de Bruijn variable and simplify.""" if body is TOP: return body elif body is BOT: return body elif is_atom(body): return ABS(body) elif is_nvar(body): return ABS(body) elif is_ivar(body): return ABS(body) elif is_abs(body): return ABS(body) elif is_join(body): lhs = abstract(body[1]) rhs = abstract(body[2]) return join(lhs, rhs) elif is_app(body): if body[2] is IVAR(0) and is_const(body[1]): # Eta contract. return decrement_rank(body[1]) else: return ABS(body) elif is_quote(body): return ABS(body) else: raise ValueError(body) raise UnreachableError(body)
def _try_decrement_rank(code, min_rank): if is_atom(code): return code elif is_nvar(code): return code elif is_ivar(code): rank = code[1] if rank < min_rank: return code elif rank == min_rank: raise CannotDecrementRank return IVAR(rank - 1) elif is_app(code): lhs = _try_decrement_rank(code[1], min_rank) rhs = _try_decrement_rank(code[2], min_rank) return APP(lhs, rhs) elif is_abs(code): return ABS(_try_decrement_rank(code[1], min_rank + 1)) elif is_join(code): lhs = _try_decrement_rank(code[1], min_rank) rhs = _try_decrement_rank(code[2], min_rank) return JOIN(lhs, rhs) elif is_quote(code): return code else: raise ValueError(code) raise UnreachableError((code, min_rank))
def _compute_step(code): assert not is_normal(code) if is_app(code): fun = code[1] arg = code[2] if is_abs(fun): assert not is_linear(fun), fun assert not is_linear(arg), arg body = fun[1] return substitute(body, arg, 0, True) if is_normal(fun): arg = _compute_step(arg) else: fun = _compute_step(fun) return app(fun, arg) elif is_join(code): lhs = _compute_step(code[1]) # Relies on prioritized sorting. rhs = code[2] return join(lhs, rhs) elif is_abs(code): body = _compute_step(code[1]) return abstract(body) elif is_quote(code): body = _compute_step(code[1]) return QUOTE(body) else: raise ValueError(code) raise UnreachableError(code)
def dump(code, f): head = code args = [] while is_app(head): args.append(head[2]) head = head[1] if is_nvar(head): _dump_head_argc(SYMB_TO_INT[_NVAR], 1 + len(args), f) _dump_raw_bytes(head[1], f) elif is_ivar(head): _dump_head_argc(SYMB_TO_INT[_IVAR], 1 + len(args), f) _dump_raw_bytes(str(head[1]), f) elif is_join(head): args.append(head[2]) args.append(head[1]) _dump_head_argc(SYMB_TO_INT[_JOIN], len(args), f) elif is_quote(head): args.append(head[1]) _dump_head_argc(SYMB_TO_INT[_QUOTE], len(args), f) elif is_abs(head): args.append(head[1]) _dump_head_argc(SYMB_TO_INT[_ABS], len(args), f) elif is_fun(head): args.append(head[2]) args.append(head[1]) _dump_head_argc(SYMB_TO_INT[_FUN], len(args), f) else: try: head = SYMB_TO_INT[head] except KeyError: raise ValueError('Failed to serialize code: {}'.format(code)) _dump_head_argc(head, len(args), f) for arg in reversed(args): dump(arg, f)
def _reduce(code, nonlinear): if is_app(code): return _app(code[1], code[2], nonlinear) elif is_join(code): return _join(code[1], code[2], nonlinear) elif is_quote(code): return QUOTE(_reduce(code[1], False)) elif is_atom(code) or is_nvar(code): return code else: raise NotImplementedError(code)
def occurs(code, rank): if is_atom(code) or is_nvar(code) or is_quote(code): return False elif is_ivar(code): return code[1] == rank elif is_app(code): return occurs(code[1], rank) or occurs(code[2], rank) elif is_abs(code): return occurs(code[1], rank + 1) elif is_join(code): return occurs(code[1], rank) or occurs(code[2], rank) else: raise ValueError(code) raise UnreachableError((code, rank))
def substitute(body, value, rank, budget): """Substitute value for IVAR(rank) in body, decremeting higher IVARs. This is linear-eager, and will be lazy about nonlinear substitutions. """ assert budget in (True, False), budget if is_atom(body): return body elif is_nvar(body): return body elif is_ivar(body): if body[1] == rank: return value elif body[1] > rank: return IVAR(body[1] - 1) else: return body elif is_app(body): lhs = body[1] rhs = body[2] linear = (is_cheap_to_copy(value) or is_const(lhs, rank) or is_const(rhs, rank)) if linear or budget: # Eager substitution. if not linear: budget = False lhs = substitute(lhs, value, rank, False) rhs = substitute(rhs, value, rank, False) return app(lhs, rhs) else: # Lazy substitution. return APP(ABS(body), value) elif is_abs(body): body = substitute(body[1], increment_rank(value, 0), rank + 1, budget) return abstract(body) elif is_join(body): lhs = substitute(body[1], value, rank, budget) rhs = substitute(body[2], value, rank, budget) return join(lhs, rhs) elif is_quote(body): return body else: raise ValueError(body) raise UnreachableError((body, value, rank, budget))
def is_normal(code): """Returns whether code is in linear normal form.""" if is_atom(code) or is_nvar(code) or is_ivar(code): return True elif is_abs(code): return is_normal(code[1]) elif is_app(code): if is_abs(code[1]): return False return is_normal(code[1]) and is_normal(code[2]) elif is_join(code): return is_normal(code[1]) and is_normal(code[2]) elif is_quote(code): return is_normal(code[1]) else: raise ValueError(code) raise UnreachableError(code)
def try_cast_code(x): """Weak oracle closing x to type CODE. Inputs: x : code in linear normal form Returns: TOP, BOT, QUOTE(...), APP(QQUOTE, ...), APP(APP(QAPP, ...), ...), or None """ assert x is not None if x is TOP or x is BOT or is_quote(x): return x if is_app(x): if x[1] is QQUOTE: return x if is_app(x[1]) and x[1][1] is QAPP: return x return None
def anonymize(code, var, rank): """Convert a nominal variable to a de Bruijn variable.""" if code is var: return IVAR(rank) elif is_atom(code) or is_nvar(code) or is_quote(code): return code elif is_ivar(code): return code if code[1] < rank else IVAR(code[1] + 1) elif is_abs(code): body = anonymize(code[1], var, rank + 1) return abstract(body) elif is_app(code): lhs = anonymize(code[1], var, rank) rhs = anonymize(code[2], var, rank) return app(lhs, rhs) elif is_join(code): lhs = anonymize(code[1], var, rank) rhs = anonymize(code[2], var, rank) return join(lhs, rhs) else: raise ValueError(code)
def compile_(code): if is_atom(code): return code elif is_nvar(code): return code elif is_app(code): x = compile_(code[1]) y = compile_(code[2]) return APP(x, y) elif is_join(code): x = compile_(code[1]) y = compile_(code[2]) return JOIN(x, y) elif is_quote(code): arg = compile_(code[1]) return QUOTE(arg) elif is_fun(code): var = code[1] body = compile_(code[2]) return abstract(var, body) else: raise ValueError('Cannot compile_: {}'.format(code))
def _is_linear(code): """ Returns: either None if code is nonlinear, else a pair (L, N) of frozensets, where L is the set of free IVARs appearing exactly once, and N is the set of free IVARs appearing multiply. """ if is_atom(code): return EMPTY_SET, EMPTY_SET elif is_nvar(code): return EMPTY_SET, EMPTY_SET elif is_ivar(code): rank = code[1] return frozenset([rank]), EMPTY_SET elif is_app(code): lhs = _is_linear(code[1]) rhs = _is_linear(code[2]) if lhs is None or rhs is None: return None return lhs[0] | rhs[0], lhs[1] | rhs[1] | (lhs[0] & rhs[0]) elif is_abs(code): body = _is_linear(code[1]) if body is None or 0 in body[1]: return None return ( frozenset(r - 1 for r in body[0] if r), frozenset(r - 1 for r in body[1]), ) elif is_join(code): lhs = _is_linear(code[1]) rhs = _is_linear(code[2]) if lhs is None or rhs is None: return None return lhs[0] | rhs[0], lhs[1] | rhs[1] elif is_quote(code): return EMPTY_SET, EMPTY_SET else: raise ValueError(code) raise UnreachableError(code)
def substitute(var, defn, body): if not is_nvar(var): raise ValueError('Expected a nominal variable, got {}'.format(var)) if is_atom(body): return body elif is_nvar(body): if body is var: return defn else: return body elif is_app(body): lhs = substitute(var, defn, body[1]) rhs = substitute(var, defn, body[2]) return APP(lhs, rhs) elif is_join(body): lhs = substitute(var, defn, body[1]) rhs = substitute(var, defn, body[2]) return JOIN(lhs, rhs) elif is_quote(body): arg = body[1] return QUOTE(substitute(var, defn, arg)) else: raise ValueError(body)
def increment_rank(code, min_rank): """Increment rank of all IVARs in code.""" if is_atom(code): return code elif is_nvar(code): return code elif is_ivar(code): rank = code[1] return IVAR(rank + 1) if rank >= min_rank else code elif is_abs(code): return ABS(increment_rank(code[1], min_rank + 1)) elif is_app(code): lhs = increment_rank(code[1], min_rank) rhs = increment_rank(code[2], min_rank) return APP(lhs, rhs) elif is_join(code): lhs = increment_rank(code[1], min_rank) rhs = increment_rank(code[2], min_rank) return JOIN(lhs, rhs) elif is_quote(code): return code else: raise ValueError(code) raise UnreachableError((code, min_rank))
def approximate(code, direction): result = set() if is_atom(code) or is_var(code) or is_quote(code): result.add(code) elif is_app(code): if is_abs(code[1]): for fun_body in approximate_var(code[1][1], direction, 0): for lhs in approximate(abstract(fun_body), direction): for rhs in approximate(code[2], direction): result.add(app(lhs, rhs)) else: for lhs in approximate(code[1], direction): for rhs in approximate(code[2], direction): result.add(app(lhs, rhs)) elif is_abs(code): for body in approximate(code[1], direction): result.add(abstract(body)) elif is_join(code): for lhs in approximate(code[1], direction): for rhs in approximate(code[2], direction): result.add(join(lhs, rhs)) else: raise ValueError(code) return tuple(sorted(result, key=complexity))
def _sample(head, context, nonlinear): """FIFO-scheduled sampler.""" # Head reduce. while True: # LOG.debug('head = {}'.format(pretty(head))) PROFILE_COUNTERS[ _sample, head[0] if isinstance(head, tuple) else head] += 1 if is_app(head): context = context_push(context, head[2]) head = head[1] elif is_nvar(head): yield head, context return elif is_join(head): for head in head[1:]: for continuation in _sample(head, context, nonlinear): yield continuation elif is_quote(head): x = head[1] x = _reduce(x, False) head = QUOTE(x) yield head, context return elif head is TOP: yield TOP, EMPTY_CONTEXT return elif head is BOT: return elif head is I: head, context = context_pop(context) elif head is K: x, context = context_pop(context) y, context = context_pop(context, x) head = x elif head is B: x, context = context_pop(context) y, context = context_pop(context, x) z, context = context_pop(context, x, y) head = x context = context_push(context, _app(y, z, False)) elif head is C: x, context = context_pop(context) y, context = context_pop(context, x) z, context = context_pop(context, x, y) head = x context = context_push(context, y) context = context_push(context, z) elif head is S: old_context = context x, context = context_pop(context) y, context = context_pop(context, x) z, context = context_pop(context, x, y) if nonlinear or is_nvar(z): head = x context = context_push(context, _app(y, z, False)) context = context_push(context, z) else: yield head, old_context return elif head is EVAL: x, context = context_pop(context) x = _reduce(x, nonlinear) if is_quote(x): head = x[1] elif x is TOP: yield TOP, EMPTY_CONTEXT return elif x is BOT: return else: head = APP(EVAL, x) yield head, context return elif head is QQUOTE: x, context = context_pop(context) x = _reduce(x, nonlinear) if x is TOP: yield TOP, EMPTY_CONTEXT return elif x is BOT: return elif is_quote(x): head = QUOTE(x) else: head = APP(QQUOTE, x) yield head, context return elif head is QAPP: x, context = context_pop(context) y, context = context_pop(context, x) x = _reduce(x, nonlinear) y = _reduce(y, nonlinear) if is_quote(x) and is_quote(y): head = QUOTE(_app(x[1], y[1], False)) else: head = APP(APP(QAPP, x), y) yield head, context return elif head in TRY_DECIDE: pred = head x, context = context_pop(context) y, context = context_pop(context, x) x = _reduce(x, nonlinear) y = _reduce(y, nonlinear) if x is TOP or y is TOP: yield TOP, EMPTY_CONTEXT return if x is BOT and y is BOT: return if pred is EQUAL: if (x is BOT and is_quote(y)) or (is_quote(x) and y is BOT): return # FIXME This sometimes fails to err when it should; see (B3). # FIXME This could be stronger by nonlinearly reducing the # arguments inside QUOTE(-) of the quoted terms; see (B4). answer = TRY_DECIDE[pred](try_unquote(x), try_unquote(y)) head = TROOL_TO_CODE[answer] if head is None: head = APP(APP(pred, x), y) yield head, context return elif head in TRY_CAST: type_ = head x, context = context_pop(context) x = _reduce(x, nonlinear) while is_app(x) and x[1] is type_: x = x[2] head = TRY_CAST[type_](x) if head is None: head = APP(type_, x) yield head, context return else: raise NotImplementedError(head)
def app(fun, arg): """Apply function to argument and linearly reduce.""" if fun is TOP: return fun elif fun is BOT: return fun elif is_nvar(fun): return APP(fun, arg) elif is_ivar(fun): return APP(fun, arg) elif is_app(fun): # Try to reduce strict binary functions of quoted codes. if fun[1] in (QAPP, LESS, EQUAL): lhs = fun[2] rhs = arg if lhs is TOP or rhs is TOP: return TOP elif lhs is BOT: if rhs is BOT or is_quote(rhs): return BOT elif is_quote(lhs): if rhs is BOT: return BOT elif is_quote(rhs): if fun[1] is QAPP: return QUOTE(app(lhs[1], rhs[1])) if fun[1] is LESS: ans = try_decide_less(lhs[1], rhs[1]) elif fun[1] is EQUAL: ans = try_decide_equal(lhs[1], rhs[1]) else: raise UnreachableError(fun[1]) if ans is True: return true elif ans is False: return false return APP(fun, arg) elif is_abs(fun): body = fun[1] return substitute(body, arg, 0, False) elif is_join(fun): lhs = app(fun[1], arg) rhs = app(fun[2], arg) return join(lhs, rhs) elif is_quote(fun): return APP(fun, arg) elif fun is EVAL: if arg is TOP: return TOP elif arg is BOT: return BOT elif is_quote(arg): return arg[1] else: return APP(fun, arg) elif fun is QAPP: if arg is TOP: return TOP else: return APP(fun, arg) elif fun is QQUOTE: if arg is TOP: return TOP elif arg is BOT: return BOT elif is_quote(arg): return QUOTE(QUOTE(arg[1])) else: return APP(fun, arg) elif fun is LESS: if arg is TOP: return TOP else: return APP(fun, arg) elif fun is EQUAL: if arg is TOP: return TOP else: return APP(fun, arg) else: raise ValueError(fun) raise UnreachableError((fun, arg))
def try_unquote(code): return code[1] if is_quote(code) else None