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 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 _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 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 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 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 _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 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 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 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 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))