(Type u) is the old (Type (u+1)) (PType u) is the old (Type u) Type* is the old (Type (_+1)) PType* is the old Type* The stdlib can be compiled, but we still have > 70 broken tests See discussion at #1341
715 lines
24 KiB
C++
715 lines
24 KiB
C++
/*
|
|
Copyright (c) 2016 Microsoft Corporation. All rights reserved.
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
|
|
Author: Leonardo de Moura
|
|
*/
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include "library/trace.h"
|
|
#include "library/util.h"
|
|
#include "library/reducible.h"
|
|
#include "library/app_builder.h"
|
|
#include "library/class.h"
|
|
#include "library/constants.h"
|
|
#include "library/kernel_serializer.h"
|
|
#include "library/vm/vm_expr.h"
|
|
#include "library/tactic/tactic_state.h"
|
|
#include "library/tactic/ac_tactics.h"
|
|
|
|
namespace lean {
|
|
struct ac_manager::cache {
|
|
environment m_env;
|
|
unsigned m_reducibility_fingerprint;
|
|
unsigned m_instance_fingerprint;
|
|
expr_struct_map<optional<expr>> m_assoc_cache[2];
|
|
expr_struct_map<optional<expr>> m_comm_cache[2];
|
|
cache(environment const & env):
|
|
m_env(env),
|
|
m_reducibility_fingerprint(get_reducibility_fingerprint(env)),
|
|
m_instance_fingerprint(get_instance_fingerprint(env)) {}
|
|
};
|
|
|
|
MK_THREAD_LOCAL_GET_DEF(ac_manager::cache_ptr, get_cache_ptr);
|
|
|
|
static ac_manager::cache_ptr get_cache(environment const & env) {
|
|
auto & cache_ptr = get_cache_ptr();
|
|
if (!cache_ptr ||
|
|
!env.is_descendant(cache_ptr->m_env) ||
|
|
get_reducibility_fingerprint(env) != cache_ptr->m_reducibility_fingerprint ||
|
|
get_instance_fingerprint(env) != cache_ptr->m_instance_fingerprint) {
|
|
cache_ptr.reset();
|
|
return std::make_shared<ac_manager::cache>(env);
|
|
}
|
|
ac_manager::cache_ptr r = cache_ptr;
|
|
cache_ptr.reset();
|
|
r->m_env = env;
|
|
if (!is_eqp_declarations(env, r->m_env)) {
|
|
/* erase cache for expressions containing locals, since it is probably not useful. */
|
|
r->m_assoc_cache[1].clear();
|
|
r->m_comm_cache[1].clear();
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static void recycle_cache(ac_manager::cache_ptr const & cache) {
|
|
get_cache_ptr() = cache;
|
|
}
|
|
|
|
ac_manager::ac_manager(type_context & ctx):
|
|
m_ctx(ctx),
|
|
m_cache_ptr(get_cache(ctx.env())) {
|
|
}
|
|
|
|
ac_manager::~ac_manager() {
|
|
recycle_cache(m_cache_ptr);
|
|
}
|
|
|
|
optional<expr> ac_manager::is_assoc(expr const & e) {
|
|
auto op = get_binary_op(e);
|
|
if (!op) return none_expr();
|
|
bool idx = has_local(e);
|
|
auto it = m_cache_ptr->m_assoc_cache[idx].find(*op);
|
|
if (it != m_cache_ptr->m_assoc_cache[idx].end())
|
|
return it->second;
|
|
optional<expr> r;
|
|
try {
|
|
expr assoc_class = mk_app(m_ctx, get_is_associative_name(), *op);
|
|
if (auto assoc_inst = m_ctx.mk_class_instance(assoc_class))
|
|
r = some_expr(mk_app(m_ctx, get_is_associative_assoc_name(), 3, *op, *assoc_inst));
|
|
} catch (app_builder_exception & ex) {}
|
|
m_cache_ptr->m_assoc_cache[idx].insert(mk_pair(*op, r));
|
|
return r;
|
|
}
|
|
|
|
optional<expr> ac_manager::is_comm(expr const & e) {
|
|
auto op = get_binary_op(e);
|
|
if (!op) return none_expr();
|
|
bool idx = has_local(e);
|
|
auto it = m_cache_ptr->m_comm_cache[idx].find(*op);
|
|
if (it != m_cache_ptr->m_comm_cache[idx].end())
|
|
return it->second;
|
|
optional<expr> r;
|
|
try {
|
|
expr comm_class = mk_app(m_ctx, get_is_commutative_name(), *op);
|
|
if (auto comm_inst = m_ctx.mk_class_instance(comm_class))
|
|
r = some_expr(mk_app(m_ctx, get_is_commutative_comm_name(), 3, *op, *comm_inst));
|
|
} catch (app_builder_exception & ex) {}
|
|
m_cache_ptr->m_comm_cache[idx].insert(mk_pair(*op, r));
|
|
return r;
|
|
}
|
|
|
|
struct flat_assoc_fn {
|
|
abstract_type_context & m_ctx;
|
|
expr m_op;
|
|
expr m_assoc;
|
|
|
|
flat_assoc_fn(abstract_type_context & ctx, expr const & op, expr const & assoc):
|
|
m_ctx(ctx), m_op(op), m_assoc(assoc) {}
|
|
|
|
bool is_op_app(expr const & e, expr & lhs, expr & rhs) {
|
|
if (!is_app(e)) return false;
|
|
expr const & fn1 = app_fn(e);
|
|
if (!is_app(fn1)) return false;
|
|
if (app_fn(fn1) != m_op) return false;
|
|
lhs = app_arg(fn1);
|
|
rhs = app_arg(e);
|
|
return true;
|
|
}
|
|
|
|
bool is_op_app(expr const & e) {
|
|
if (!is_app(e)) return false;
|
|
expr const & fn1 = app_fn(e);
|
|
if (!is_app(fn1)) return false;
|
|
return app_fn(fn1) == m_op;
|
|
}
|
|
|
|
expr mk_op(expr const & a, expr const & b) {
|
|
return mk_app(m_op, a, b);
|
|
}
|
|
|
|
expr mk_assoc(expr const & a, expr const & b, expr const & c) {
|
|
return mk_app(m_assoc, a, b, c);
|
|
}
|
|
|
|
expr mk_eq_refl(expr const & a) {
|
|
return ::lean::mk_eq_refl(m_ctx, a);
|
|
}
|
|
|
|
expr mk_eq_trans(expr const & H1, expr const & H2) {
|
|
return ::lean::mk_eq_trans(m_ctx, H1, H2);
|
|
}
|
|
|
|
expr mk_eq_trans(expr const & H1, optional<expr> const & H2) {
|
|
if (!H2) return H1;
|
|
return mk_eq_trans(H1, *H2);
|
|
}
|
|
|
|
optional<expr> mk_eq_trans(optional<expr> const & H1, optional<expr> const & H2) {
|
|
if (!H1) return H2;
|
|
if (!H2) return H1;
|
|
return some_expr(mk_eq_trans(*H1, *H2));
|
|
}
|
|
|
|
expr mk_eq_symm(expr const & H) {
|
|
return ::lean::mk_eq_symm(m_ctx, H);
|
|
}
|
|
|
|
optional<expr> mk_eq_symm(optional<expr> const & H) {
|
|
if (!H) return none_expr();
|
|
return some_expr(mk_eq_symm(*H));
|
|
}
|
|
|
|
expr mk_congr_arg(expr const & fn, expr const & H) {
|
|
return ::lean::mk_congr_arg(m_ctx, fn, H);
|
|
}
|
|
|
|
pair<expr, optional<expr>> flat_with(expr const & e, expr const & rest) {
|
|
expr lhs, rhs;
|
|
if (is_op_app(e, lhs, rhs)) {
|
|
auto p1 = flat_with(rhs, rest);
|
|
if (p1.second) {
|
|
auto p2 = flat_with(lhs, p1.first);
|
|
// H3 is a proof for (lhs `op` rhs) `op` rest = lhs `op` (rhs `op` rest)
|
|
expr H3 = mk_assoc(lhs, rhs, rest);
|
|
// H4 is a proof for lhs `op` (rhs `op` rest) = lhs `op` p1.first
|
|
expr H4 = mk_congr_arg(mk_app(m_op, lhs), *p1.second);
|
|
expr H = mk_eq_trans(mk_eq_trans(H3, H4), p2.second);
|
|
return mk_pair(p2.first, some_expr(H));
|
|
} else {
|
|
if (is_op_app(lhs)) {
|
|
auto p2 = flat_with(lhs, p1.first);
|
|
// H3 is a proof for (lhs `op` rhs) `op` rest = lhs `op` (rhs `op` rest)
|
|
expr H3 = mk_assoc(lhs, rhs, rest);
|
|
expr H = mk_eq_trans(H3, p2.second);
|
|
return mk_pair(p2.first, some_expr(H));
|
|
} else {
|
|
return mk_pair(mk_op(lhs, p1.first), some_expr(mk_assoc(lhs, rhs, rest)));
|
|
}
|
|
}
|
|
} else {
|
|
return mk_pair(mk_op(e, rest), none_expr());
|
|
}
|
|
}
|
|
|
|
pair<expr, optional<expr>> flat_core(expr const & e) {
|
|
expr lhs, rhs;
|
|
if (is_op_app(e, lhs, rhs)) {
|
|
auto p1 = flat_core(rhs);
|
|
if (p1.second) {
|
|
if (is_op_app(lhs)) {
|
|
auto p2 = flat_with(lhs, p1.first);
|
|
expr H3 = mk_congr_arg(mk_app(m_op, lhs), *p1.second);
|
|
expr H = mk_eq_trans(H3, p2.second);
|
|
return mk_pair(p2.first, some_expr(H));
|
|
} else {
|
|
expr r = mk_op(lhs, p1.first);
|
|
expr H = mk_congr_arg(mk_app(m_op, lhs), *p1.second);
|
|
return mk_pair(r, some_expr(H));
|
|
}
|
|
} else {
|
|
if (is_op_app(lhs)) {
|
|
return flat_with(lhs, rhs);
|
|
} else {
|
|
return mk_pair(e, none_expr());
|
|
}
|
|
}
|
|
} else {
|
|
return mk_pair(e, none_expr());
|
|
}
|
|
}
|
|
|
|
pair<expr, expr> flat(expr const & e) {
|
|
auto p = flat_core(e);
|
|
if (p.second) {
|
|
return mk_pair(p.first, *p.second);
|
|
} else {
|
|
return mk_pair(e, mk_eq_refl(e));
|
|
}
|
|
}
|
|
};
|
|
|
|
#define lean_perm_ac_trace(code) lean_trace(name({"tactic", "perm_ac"}), scope_trace_env _scope1(m_ctx.env(), m_ctx); code)
|
|
|
|
struct perm_ac_fn : public flat_assoc_fn {
|
|
expr m_comm;
|
|
optional<expr> m_left_comm;
|
|
|
|
perm_ac_fn(abstract_type_context & ctx, expr const & op, expr const & assoc, expr const & comm):
|
|
flat_assoc_fn(ctx, op, assoc), m_comm(comm) {
|
|
}
|
|
|
|
[[ noreturn ]] void throw_failed() {
|
|
throw exception("perm_ac failed, arguments are not equal modulo AC");
|
|
}
|
|
|
|
expr mk_comm(expr const & a, expr const & b) {
|
|
return mk_app(m_comm, a, b);
|
|
}
|
|
|
|
level dec_level(level const & l) {
|
|
if (auto r = ::lean::dec_level(l))
|
|
return *r;
|
|
throw_failed();
|
|
}
|
|
|
|
expr mk_left_comm(expr const & a, expr const & b, expr const & c) {
|
|
if (!m_left_comm) {
|
|
expr A = m_ctx.infer(a);
|
|
level lvl = dec_level(get_level(m_ctx, A));
|
|
m_left_comm = mk_app(mk_constant(get_left_comm_name(), {lvl}), A, m_op, m_comm, m_assoc);
|
|
}
|
|
return mk_app(*m_left_comm, a, b, c);
|
|
}
|
|
|
|
/* Given a term \c e of the form (op t_1 (op t_2 ... (op t_{n-1} t_n))), if
|
|
for some i, t_i == t, then produce the term
|
|
|
|
(op t_i (op t_2 ... (op t_{n-1} t_n)))
|
|
|
|
and a proof they are equal AC.
|
|
Throw exception if t is not found. */
|
|
pair<expr, expr> pull_term(expr const & t, expr const & e) {
|
|
expr lhs1, rhs1;
|
|
if (!is_op_app(e, lhs1, rhs1)) {
|
|
lean_perm_ac_trace(tout() << "right-hand-side does not contain:\n" << t << "\n";);
|
|
throw_failed();
|
|
}
|
|
if (t == rhs1) {
|
|
return mk_pair(mk_op(rhs1, lhs1), mk_comm(lhs1, rhs1));
|
|
}
|
|
expr lhs2, rhs2;
|
|
if (!is_op_app(rhs1, lhs2, rhs2)) {
|
|
lean_perm_ac_trace(tout() << "right-hand-side does not contain:\n" << t << "\n";);
|
|
throw_failed();
|
|
}
|
|
if (t == lhs2) {
|
|
return mk_pair(mk_op(lhs2, mk_op(lhs1, rhs2)), mk_left_comm(lhs1, lhs2, rhs2));
|
|
}
|
|
/* We have e := lhs1 `op` lhs2 `op` rhs2 */
|
|
auto p = pull_term(t, rhs1);
|
|
expr lhs3, rhs3;
|
|
lean_verify(is_op_app(p.first, lhs3, rhs3));
|
|
lean_assert(t == lhs3);
|
|
/* p.second : rhs1 = t `op` rhs3 */
|
|
expr H1 = mk_congr_arg(mk_app(m_op, lhs1), p.second);
|
|
/* H1 : lhs1 `op` rhs1 = lhs1 `op` t `op` rhs3 */
|
|
expr H2 = mk_left_comm(lhs1, t, rhs3);
|
|
/* H2 : lhs1 `op` t `op` rhs3 = t `op` lhs1 `op` rhs3 */
|
|
return mk_pair(mk_op(t, mk_op(lhs1, rhs3)), mk_eq_trans(H1, H2));
|
|
}
|
|
|
|
/* Return a proof that e1 == e2 modulo AC. Return none if reflexivity.
|
|
Throw exception if failure */
|
|
optional<expr> perm_flat(expr const & e1, expr const & e2) {
|
|
expr lhs1, rhs1;
|
|
expr lhs2, rhs2;
|
|
bool b1 = is_op_app(e1, lhs1, rhs1);
|
|
bool b2 = is_op_app(e2, lhs2, rhs2);
|
|
if (b1 != b2) {
|
|
lean_perm_ac_trace(tout() << "left and right-hand-sides have different number of terms\n";);
|
|
throw_failed();
|
|
}
|
|
if (!b1 && !b2) {
|
|
if (e1 == e2) {
|
|
return none_expr(); // reflexivity
|
|
} else {
|
|
lean_perm_ac_trace(tout() << "the left and right hand sides contain the terms:\n" << e1 << "\n" << e2 << "\n";);
|
|
throw_failed();
|
|
}
|
|
}
|
|
lean_assert(b1 && b2);
|
|
if (lhs1 == lhs2) {
|
|
optional<expr> H = perm_flat(rhs1, rhs2);
|
|
if (!H) return none_expr();
|
|
return some_expr(mk_congr_arg(mk_app(m_op, lhs1), *H));
|
|
} else {
|
|
auto p = pull_term(lhs2, e1);
|
|
is_op_app(p.first, lhs1, rhs1);
|
|
lean_assert(lhs1 == lhs2);
|
|
optional<expr> H1 = perm_flat(rhs1, rhs2);
|
|
if (!H1) return some_expr(p.second);
|
|
expr H2 = mk_congr_arg(mk_app(m_op, lhs1), *H1);
|
|
return some_expr(mk_eq_trans(p.second, H2));
|
|
}
|
|
}
|
|
|
|
/* Return a proof that lhs == rhs modulo AC. Return none if reflexivity.
|
|
Throw exception if failure */
|
|
optional<expr> perm_core(expr const & lhs, expr const & rhs) {
|
|
auto p1 = flat_core(lhs);
|
|
auto p2 = flat_core(rhs);
|
|
auto H = perm_flat(p1.first, p2.first);
|
|
return mk_eq_trans(p1.second, mk_eq_trans(H, mk_eq_symm(p2.second)));
|
|
}
|
|
|
|
expr perm(expr const & lhs, expr const & rhs) {
|
|
if (auto H = perm_core(lhs, rhs))
|
|
return *H;
|
|
else
|
|
return mk_eq_refl(lhs);
|
|
}
|
|
};
|
|
|
|
pair<expr, optional<expr>> flat_assoc(abstract_type_context & ctx, expr const & op, expr const & assoc, expr const & e) {
|
|
return flat_assoc_fn(ctx, op, assoc).flat_core(e);
|
|
}
|
|
|
|
expr perm_ac(abstract_type_context & ctx, expr const & op, expr const & assoc, expr const & comm, expr const & e1, expr const & e2) {
|
|
return perm_ac_fn(ctx, op, assoc, comm).perm(e1, e2);
|
|
}
|
|
|
|
static name * g_ac_app_name = nullptr;
|
|
static macro_definition * g_ac_app_macro = nullptr;
|
|
static std::string * g_ac_app_opcode = nullptr;
|
|
|
|
static expr expand_ac_core(expr const & e) {
|
|
unsigned nargs = macro_num_args(e);
|
|
unsigned i = nargs - 1;
|
|
expr const & op = macro_arg(e, i);
|
|
--i;
|
|
expr r = macro_arg(e, i);
|
|
while (i > 0) {
|
|
--i;
|
|
r = mk_app(op, macro_arg(e, i), r);
|
|
}
|
|
return r;
|
|
}
|
|
|
|
class ac_app_macro_cell : public macro_definition_cell {
|
|
public:
|
|
virtual name get_name() const { return *g_ac_app_name; }
|
|
|
|
virtual unsigned trust_level() const { return LEAN_AC_MACROS_TRUST_LEVEL; }
|
|
|
|
virtual expr check_type(expr const & e, abstract_type_context & ctx, bool) const {
|
|
return ctx.infer(macro_arg(e, 0));
|
|
}
|
|
|
|
virtual optional<expr> expand(expr const & e, abstract_type_context &) const {
|
|
return some_expr(expand_ac_core(e));
|
|
}
|
|
|
|
virtual void write(serializer & s) const {
|
|
s.write_string(*g_ac_app_opcode);
|
|
}
|
|
|
|
virtual bool operator==(macro_definition_cell const & other) const {
|
|
ac_app_macro_cell const * other_ptr = dynamic_cast<ac_app_macro_cell const *>(&other);
|
|
return other_ptr;
|
|
}
|
|
|
|
virtual unsigned hash() const { return 37; }
|
|
};
|
|
|
|
static expr mk_ac_app_core(unsigned nargs, expr const * args_op) {
|
|
lean_assert(nargs >= 3);
|
|
return mk_macro(*g_ac_app_macro, nargs, args_op);
|
|
}
|
|
|
|
static expr mk_ac_app_core(expr const & op, buffer<expr> & args) {
|
|
lean_assert(args.size() >= 2);
|
|
args.push_back(op);
|
|
expr r = mk_ac_app_core(args.size(), args.data());
|
|
args.pop_back();
|
|
return r;
|
|
}
|
|
|
|
expr mk_ac_app(expr const & op, buffer<expr> & args) {
|
|
lean_assert(args.size() > 0);
|
|
if (args.size() == 1) {
|
|
return args[0];
|
|
} else {
|
|
std::sort(args.begin(), args.end(), is_hash_lt);
|
|
return mk_ac_app_core(op, args);
|
|
}
|
|
}
|
|
|
|
bool is_ac_app(expr const & e) {
|
|
return is_macro(e) && is_eqp(macro_def(e), *g_ac_app_macro);
|
|
}
|
|
|
|
expr const & get_ac_app_op(expr const & e) {
|
|
lean_assert(is_ac_app(e));
|
|
return macro_arg(e, macro_num_args(e) - 1);
|
|
}
|
|
|
|
unsigned get_ac_app_num_args(expr const & e) {
|
|
lean_assert(is_ac_app(e));
|
|
return macro_num_args(e) - 1;
|
|
}
|
|
|
|
expr const * get_ac_app_args(expr const & e) {
|
|
lean_assert(is_ac_app(e));
|
|
return macro_args(e);
|
|
}
|
|
|
|
/* Return true iff e1 occurs in e2.
|
|
Example ac_mem(b, a*a*b*c) returns true. */
|
|
static bool ac_mem(expr const & e1, expr const & e2) {
|
|
unsigned nargs2 = get_ac_app_num_args(e2);
|
|
expr const * args2 = get_ac_app_args(e2);
|
|
return std::find(args2, args2+nargs2, e1) != args2+nargs2;
|
|
}
|
|
|
|
/* Return true iff e1 is a "subset" of e2.
|
|
Example: The result is true for e1 := (a*a*a*b*d) and e2 := (a*a*a*a*b*b*c*d*d) */
|
|
bool is_ac_subset(expr const & e1, expr const & e2) {
|
|
if (is_ac_app(e1)) {
|
|
if (is_ac_app(e2)) {
|
|
if (get_ac_app_op(e1) == get_ac_app_op(e2)) {
|
|
unsigned nargs1 = get_ac_app_num_args(e1);
|
|
unsigned nargs2 = get_ac_app_num_args(e2);
|
|
if (nargs1 > nargs2) return false;
|
|
expr const * args1 = get_ac_app_args(e1);
|
|
expr const * args2 = get_ac_app_args(e2);
|
|
unsigned i1 = 0;
|
|
unsigned i2 = 0;
|
|
while (i1 < nargs1 && i2 < nargs2) {
|
|
if (args1[i1] == args2[i2]) {
|
|
i1++;
|
|
i2++;
|
|
} else if (is_hash_lt(args2[i2], args1[i1])) {
|
|
i2++;
|
|
} else {
|
|
lean_assert(is_hash_lt(args1[i1], args2[i2]));
|
|
return false;
|
|
}
|
|
}
|
|
return i1 == nargs1;
|
|
} else {
|
|
lean_assert(get_ac_app_op(e1) != get_ac_app_op(e2));
|
|
/* treat e1 as an atomic value that may occur in e2 */
|
|
return ac_mem(e1, e2);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (is_ac_app(e2)) {
|
|
return ac_mem(e1, e2);
|
|
} else {
|
|
return e1 == e2;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Store in r e1\e2.
|
|
Example: given e1 := (a*a*a*a*b*b*c*d*d*d) and e2 := (a*a*a*b*b*d),
|
|
the result is (a, c, d, d)
|
|
|
|
\pre is_ac_subset(e2, e1) */
|
|
void ac_diff(expr const & e1, expr const & e2, buffer<expr> & r) {
|
|
lean_assert(is_ac_subset(e2, e1));
|
|
if (is_ac_app(e1)) {
|
|
if (is_ac_app(e2) && get_ac_app_op(e1) == get_ac_app_op(e2)) {
|
|
unsigned nargs1 = get_ac_app_num_args(e1);
|
|
unsigned nargs2 = get_ac_app_num_args(e2);
|
|
lean_assert(nargs1 >= nargs2);
|
|
expr const * args1 = get_ac_app_args(e1);
|
|
expr const * args2 = get_ac_app_args(e2);
|
|
unsigned i2 = 0;
|
|
for (unsigned i1 = 0; i1 < nargs1; i1++) {
|
|
if (i2 == nargs2) {
|
|
r.push_back(args1[i1]);
|
|
} else if (args1[i1] == args2[i2]) {
|
|
i2++;
|
|
} else {
|
|
lean_assert(is_hash_lt(args1[i1], args2[i2]));
|
|
r.push_back(args1[i1]);
|
|
}
|
|
}
|
|
} else {
|
|
bool found = false;
|
|
unsigned nargs1 = get_ac_app_num_args(e1);
|
|
expr const * args1 = get_ac_app_args(e1);
|
|
for (unsigned i = 0; i < nargs1; i++) {
|
|
if (!found && args1[i] == e2) {
|
|
found = true;
|
|
} else {
|
|
r.push_back(args1[i]);
|
|
}
|
|
}
|
|
lean_assert(found);
|
|
}
|
|
} else {
|
|
lean_assert(!is_ac_app(e1));
|
|
lean_assert(!is_ac_app(e2));
|
|
lean_assert(e1 == e2);
|
|
}
|
|
}
|
|
|
|
void ac_append(expr const & op, expr const & e, buffer<expr> & r) {
|
|
if (is_ac_app(e) && get_ac_app_op(e) == op) {
|
|
r.append(get_ac_app_num_args(e), get_ac_app_args(e));
|
|
} else {
|
|
r.push_back(e);
|
|
}
|
|
}
|
|
|
|
void ac_intersection(expr const & e1, expr const & e2, buffer<expr> & r) {
|
|
lean_assert(is_ac_app(e1));
|
|
lean_assert(is_ac_app(e2));
|
|
lean_assert(get_ac_app_op(e1) == get_ac_app_op(e2));
|
|
unsigned nargs1 = get_ac_app_num_args(e1);
|
|
unsigned nargs2 = get_ac_app_num_args(e2);
|
|
expr const * args1 = get_ac_app_args(e1);
|
|
expr const * args2 = get_ac_app_args(e2);
|
|
unsigned i1 = 0;
|
|
unsigned i2 = 0;
|
|
while (i1 < nargs1 && i2 < nargs2) {
|
|
if (args1[i1] == args2[i2]) {
|
|
r.push_back(args1[i1]);
|
|
i1++;
|
|
i2++;
|
|
} else if (is_hash_lt(args2[i2], args1[i1])) {
|
|
i2++;
|
|
} else {
|
|
lean_assert(is_hash_lt(args1[i1], args2[i2]));
|
|
i1++;
|
|
}
|
|
}
|
|
}
|
|
|
|
expr mk_ac_flat_app(expr const & op, expr const & e1, expr const & e2) {
|
|
buffer<expr> new_args;
|
|
ac_append(op, e1, new_args);
|
|
ac_append(op, e2, new_args);
|
|
return mk_ac_app(op, new_args);
|
|
}
|
|
|
|
/* lexdeg order */
|
|
bool ac_lt(expr const & e1, expr const & e2) {
|
|
if (is_ac_app(e1)) {
|
|
if (is_ac_app(e2) && get_ac_app_op(e1) == get_ac_app_op(e2)) {
|
|
unsigned nargs1 = get_ac_app_num_args(e1);
|
|
unsigned nargs2 = get_ac_app_num_args(e2);
|
|
if (nargs1 < nargs2) return true;
|
|
if (nargs1 > nargs2) return false;
|
|
expr const * args1 = get_ac_app_args(e1);
|
|
expr const * args2 = get_ac_app_args(e2);
|
|
for (unsigned i = 0; i < nargs1; i++) {
|
|
if (args1[i] != args2[i])
|
|
return is_hash_lt(args1[i], args2[i]);
|
|
}
|
|
return false;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
if (is_ac_app(e2)) {
|
|
return true;
|
|
} else {
|
|
return is_hash_lt(e1, e2);
|
|
}
|
|
}
|
|
}
|
|
|
|
static expr expand_if_ac_app(expr const & e) {
|
|
if (is_ac_app(e))
|
|
return expand_ac_core(e);
|
|
else
|
|
return e;
|
|
}
|
|
|
|
static name * g_perm_ac_name = nullptr;
|
|
static macro_definition * g_perm_ac_macro = nullptr;
|
|
static std::string * g_perm_ac_opcode = nullptr;
|
|
|
|
class perm_ac_macro_cell : public macro_definition_cell {
|
|
public:
|
|
virtual name get_name() const { return *g_perm_ac_name; }
|
|
|
|
virtual expr check_type(expr const & e, abstract_type_context & ctx, bool) const {
|
|
return mk_eq(ctx, macro_arg(e, 2), macro_arg(e, 3));
|
|
}
|
|
|
|
virtual unsigned trust_level() const { return LEAN_AC_MACROS_TRUST_LEVEL; }
|
|
|
|
virtual optional<expr> expand(expr const & e, abstract_type_context & ctx) const {
|
|
expr const & assoc = macro_arg(e, 0);
|
|
expr const & comm = macro_arg(e, 1);
|
|
expr e1 = expand_if_ac_app(macro_arg(e, 2));
|
|
expr e2 = expand_if_ac_app(macro_arg(e, 3));
|
|
expr const & op = app_fn(app_fn(e1));
|
|
return some_expr(perm_ac(ctx, op, assoc, comm, e1, e2));
|
|
}
|
|
|
|
virtual void write(serializer & s) const {
|
|
s.write_string(*g_perm_ac_opcode);
|
|
}
|
|
|
|
virtual bool operator==(macro_definition_cell const & other) const {
|
|
perm_ac_macro_cell const * other_ptr = dynamic_cast<perm_ac_macro_cell const *>(&other);
|
|
return other_ptr;
|
|
}
|
|
|
|
virtual unsigned hash() const { return 31; }
|
|
};
|
|
|
|
expr mk_perm_ac_macro_core(expr const & assoc, expr const & comm, expr const & e1, expr const & e2) {
|
|
lean_assert((get_binary_op(e1) || is_ac_app(e1)) && (get_binary_op(e2) || is_ac_app(e2)));
|
|
expr args[4] = {assoc, comm, e1, e2};
|
|
return mk_macro(*g_perm_ac_macro, 4, args);
|
|
}
|
|
|
|
expr mk_perm_ac_macro(abstract_type_context & ctx, expr const & assoc, expr const & comm, expr const & e1, expr const & e2) {
|
|
if (e1 == e2) {
|
|
return mk_eq_refl(ctx, e1);
|
|
} else {
|
|
return mk_perm_ac_macro_core(assoc, comm, e1, e2);
|
|
}
|
|
}
|
|
|
|
#define TRY LEAN_TACTIC_TRY
|
|
#define CATCH LEAN_TACTIC_CATCH(to_tactic_state(s))
|
|
|
|
vm_obj tactic_flat_assoc(vm_obj const & op, vm_obj const & assoc, vm_obj const & e, vm_obj const & s) {
|
|
TRY;
|
|
type_context ctx = mk_type_context_for(s);
|
|
pair<expr, expr> p = flat_assoc_fn(ctx, to_expr(op), to_expr(assoc)).flat(to_expr(e));
|
|
return mk_tactic_success(mk_vm_pair(to_obj(p.first), to_obj(p.second)), to_tactic_state(s));
|
|
CATCH;
|
|
}
|
|
|
|
vm_obj tactic_perm_ac(vm_obj const & op, vm_obj const & assoc, vm_obj const & comm, vm_obj const & e1, vm_obj const & e2, vm_obj const & s) {
|
|
TRY;
|
|
type_context ctx = mk_type_context_for(s);
|
|
expr H = perm_ac_fn(ctx, to_expr(op), to_expr(assoc), to_expr(comm)).perm(to_expr(e1), to_expr(e2));
|
|
return mk_tactic_success(to_obj(H), to_tactic_state(s));
|
|
CATCH;
|
|
}
|
|
|
|
void initialize_ac_tactics() {
|
|
register_trace_class(name{"tactic", "perm_ac"});
|
|
DECLARE_VM_BUILTIN(name({"tactic", "flat_assoc"}), tactic_flat_assoc);
|
|
DECLARE_VM_BUILTIN(name({"tactic", "perm_ac"}), tactic_perm_ac);
|
|
|
|
g_ac_app_name = new name("ac_app");
|
|
g_ac_app_opcode = new std::string("ACApp");
|
|
g_ac_app_macro = new macro_definition(new ac_app_macro_cell());
|
|
register_macro_deserializer(*g_ac_app_opcode,
|
|
[](deserializer &, unsigned num, expr const * args) {
|
|
return mk_ac_app_core(num, args);
|
|
});
|
|
|
|
g_perm_ac_name = new name("perm_ac");
|
|
g_perm_ac_opcode = new std::string("PermAC");
|
|
g_perm_ac_macro = new macro_definition(new perm_ac_macro_cell());
|
|
register_macro_deserializer(*g_perm_ac_opcode,
|
|
[](deserializer &, unsigned num, expr const * args) {
|
|
if (num != 4) corrupted_stream_exception();
|
|
return mk_perm_ac_macro_core(args[0], args[1], args[2], args[3]);
|
|
});
|
|
}
|
|
|
|
void finalize_ac_tactics() {
|
|
delete g_ac_app_name;
|
|
delete g_ac_app_opcode;
|
|
delete g_ac_app_macro;
|
|
|
|
delete g_perm_ac_name;
|
|
delete g_perm_ac_opcode;
|
|
delete g_perm_ac_macro;
|
|
}
|
|
}
|