2013-07-24 07:32:01 +00:00
|
|
|
/*
|
|
|
|
Copyright (c) 2013 Microsoft Corporation. All rights reserved.
|
|
|
|
Released under Apache 2.0 license as described in the file LICENSE.
|
|
|
|
|
|
|
|
Author: Leonardo de Moura
|
|
|
|
*/
|
|
|
|
#pragma once
|
2013-10-26 18:07:06 +00:00
|
|
|
#include <tuple>
|
2013-09-13 03:04:10 +00:00
|
|
|
#include "util/buffer.h"
|
2013-12-07 22:59:21 +00:00
|
|
|
#include "util/interrupt.h"
|
2013-09-13 03:04:10 +00:00
|
|
|
#include "kernel/expr.h"
|
|
|
|
#include "kernel/expr_maps.h"
|
2013-12-18 00:35:39 +00:00
|
|
|
#include "kernel/update_expr.h"
|
2013-09-13 03:04:10 +00:00
|
|
|
|
2013-07-24 07:32:01 +00:00
|
|
|
namespace lean {
|
2013-08-28 17:47:19 +00:00
|
|
|
/**
|
|
|
|
\brief Default replace_fn postprocessor functional object. It is a
|
|
|
|
do-nothing object.
|
|
|
|
*/
|
|
|
|
class default_replace_postprocessor {
|
|
|
|
public:
|
2013-09-20 04:26:01 +00:00
|
|
|
void operator()(expr const &, expr const &) {}
|
2013-08-28 17:47:19 +00:00
|
|
|
};
|
|
|
|
|
2013-07-24 07:32:01 +00:00
|
|
|
/**
|
2013-07-26 18:43:53 +00:00
|
|
|
\brief Functional for applying <tt>F</tt> to the subexpressions of a given expression.
|
2013-07-24 07:32:01 +00:00
|
|
|
|
2013-07-26 19:27:55 +00:00
|
|
|
The signature of \c F is
|
2013-09-17 18:09:59 +00:00
|
|
|
expr const &, unsigned -> expr
|
2013-07-24 07:32:01 +00:00
|
|
|
|
2013-07-26 19:27:55 +00:00
|
|
|
F is invoked for each subexpression \c s of the input expression e.
|
2013-09-17 18:09:59 +00:00
|
|
|
In a call <tt>F(s, n)</tt>, n is the scope level, i.e., the number of
|
2013-07-26 19:27:55 +00:00
|
|
|
bindings operators that enclosing \c s.
|
2013-08-28 17:47:19 +00:00
|
|
|
|
|
|
|
P is a "post-processing" functional object that is applied to each
|
|
|
|
pair (old, new)
|
2013-07-24 07:32:01 +00:00
|
|
|
*/
|
2013-09-13 23:14:24 +00:00
|
|
|
template<typename F, typename P = default_replace_postprocessor>
|
2013-07-24 07:45:38 +00:00
|
|
|
class replace_fn {
|
2013-08-03 03:00:26 +00:00
|
|
|
static_assert(std::is_same<typename std::result_of<F(expr const &, unsigned)>::type, expr>::value,
|
|
|
|
"replace_fn: return type of F is not expr");
|
2013-10-01 23:39:52 +00:00
|
|
|
// the return type of P()(e1, e2) should be void
|
|
|
|
static_assert(std::is_same<typename std::result_of<decltype(std::declval<P>())(expr const &, expr const &)>::type,
|
|
|
|
void>::value,
|
|
|
|
"The return type of P()(e1, e2) is not void");
|
|
|
|
|
2013-12-18 00:35:39 +00:00
|
|
|
struct frame {
|
|
|
|
expr m_expr;
|
|
|
|
unsigned m_offset;
|
|
|
|
bool m_shared;
|
|
|
|
unsigned m_index;
|
|
|
|
frame(expr const & e, unsigned o, bool s):m_expr(e), m_offset(o), m_shared(s), m_index(0) {}
|
|
|
|
};
|
|
|
|
typedef buffer<frame> frame_stack;
|
|
|
|
typedef buffer<expr> result_stack;
|
|
|
|
|
2013-07-24 07:32:01 +00:00
|
|
|
expr_cell_offset_map<expr> m_cache;
|
|
|
|
F m_f;
|
2013-08-28 17:47:19 +00:00
|
|
|
P m_post;
|
2013-12-18 00:35:39 +00:00
|
|
|
frame_stack m_fs;
|
|
|
|
result_stack m_rs;
|
|
|
|
|
|
|
|
static bool is_atomic(expr const & e) {
|
|
|
|
switch (e.kind()) {
|
2013-12-25 05:11:25 +00:00
|
|
|
case expr_kind::Constant: case expr_kind::Type: case expr_kind::Value:
|
|
|
|
case expr_kind::Var: case expr_kind::MetaVar:
|
2013-12-18 00:35:39 +00:00
|
|
|
return true;
|
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2013-07-24 07:32:01 +00:00
|
|
|
|
2013-12-18 00:35:39 +00:00
|
|
|
void save_result(expr const & e, expr const & r, unsigned offset, bool shared) {
|
|
|
|
if (shared)
|
|
|
|
m_cache.insert(std::make_pair(expr_cell_offset(e.raw(), offset), r));
|
|
|
|
m_post(e, r);
|
|
|
|
m_rs.push_back(r);
|
2013-12-08 07:21:07 +00:00
|
|
|
}
|
|
|
|
|
2013-12-18 00:35:39 +00:00
|
|
|
/**
|
|
|
|
\brief Visit \c e at the given offset. Return true iff the result is on the
|
|
|
|
result stack \c m_rs. Return false iff a new frame was pushed on the stack \c m_fs.
|
|
|
|
The idea is that after the frame is processed, the result will be on the result stack.
|
|
|
|
*/
|
|
|
|
bool visit(expr const & e, unsigned offset) {
|
|
|
|
bool shared = false;
|
2013-07-24 07:32:01 +00:00
|
|
|
if (is_shared(e)) {
|
|
|
|
expr_cell_offset p(e.raw(), offset);
|
|
|
|
auto it = m_cache.find(p);
|
2013-12-18 00:35:39 +00:00
|
|
|
if (it != m_cache.end()) {
|
|
|
|
m_rs.push_back(it->second);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
shared = true;
|
2013-07-24 07:32:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
expr r = m_f(e, offset);
|
2013-12-18 00:35:39 +00:00
|
|
|
if (is_atomic(r) || !is_eqp(e, r)) {
|
|
|
|
save_result(e, r, offset, shared);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
m_fs.emplace_back(e, offset, shared);
|
|
|
|
return false;
|
2013-07-24 07:32:01 +00:00
|
|
|
}
|
2013-12-18 00:35:39 +00:00
|
|
|
}
|
2013-07-24 07:32:01 +00:00
|
|
|
|
2013-12-18 00:35:39 +00:00
|
|
|
/**
|
|
|
|
\brief Return true iff <tt>f.m_index == idx</tt>.
|
|
|
|
When the result is true, <tt>f.m_index</tt> is incremented.
|
|
|
|
*/
|
|
|
|
bool check_index(frame & f, unsigned idx) {
|
|
|
|
if (f.m_index == idx) {
|
|
|
|
f.m_index++;
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2013-07-24 07:32:01 +00:00
|
|
|
|
2013-12-18 00:35:39 +00:00
|
|
|
expr const & rs(int i) {
|
|
|
|
lean_assert(i < 0);
|
|
|
|
return m_rs[m_rs.size() + i];
|
|
|
|
}
|
2013-08-28 17:47:19 +00:00
|
|
|
|
2013-12-18 00:35:39 +00:00
|
|
|
void pop_rs(unsigned num) {
|
|
|
|
m_rs.shrink(m_rs.size() - num);
|
2013-07-24 07:32:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public:
|
2013-08-28 17:47:19 +00:00
|
|
|
replace_fn(F const & f, P const & p = P()):
|
|
|
|
m_f(f),
|
|
|
|
m_post(p) {
|
2013-07-24 07:32:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
expr operator()(expr const & e) {
|
2013-12-18 00:35:39 +00:00
|
|
|
expr r;
|
|
|
|
visit(e, 0);
|
|
|
|
while (!m_fs.empty()) {
|
|
|
|
begin_loop:
|
|
|
|
check_interrupted();
|
|
|
|
frame & f = m_fs.back();
|
|
|
|
expr const & e = f.m_expr;
|
|
|
|
unsigned offset = f.m_offset;
|
|
|
|
switch (e.kind()) {
|
2013-12-25 05:11:25 +00:00
|
|
|
case expr_kind::Constant: case expr_kind::Type: case expr_kind::Value: case expr_kind::Var: case expr_kind::MetaVar:
|
2013-12-18 00:35:39 +00:00
|
|
|
lean_unreachable(); // LCOV_EXCL_LINE
|
refactor(kernel): add heterogeneous equality back to expr
The main motivation is that we will be able to move equalities between universes.
For example, suppose we have
A : (Type i)
B : (Type i)
H : @eq (Type j) A B
where j > i
We didn't find any trick for deducing (@eq (Type i) A B) from H.
Before this commit, heterogeneous equality as a constant with type
heq : {A B : (Type U)} : A -> B -> Bool
So, from H, we would only be able to deduce
(@heq (Type j) (Type j) A B)
Not being able to move the equality back to a smaller universe is
problematic in several cases. I list some instances in the end of the commit message.
With this commit, Heterogeneous equality is a special kind of expression.
It is not a constant anymore. From H, we can deduce
H1 : A == B
That is, we are essentially "erasing" the universes when we move to heterogeneous equality.
Now, since A and B have (Type i), we can deduce (@eq (Type i) A B) from H1. The proof term is
(to_eq (Type i) A B (to_heq (Type j) A B H)) : (@eq (Type i) A B)
So, it remains to explain why we need this feature.
For example, suppose we want to state the Pi extensionality axiom.
axiom hpiext {A A' : (Type U)} {B : A → (Type U)} {B' : A' → (Type U)} :
A = A' → (∀ x x', x == x' → B x == B' x') → (∀ x, B x) == (∀ x, B' x)
This axiom produces an "inflated" equality at (Type U) when we treat heterogeneous
equality as a constant. The conclusion
(∀ x, B x) == (∀ x, B' x)
is syntax sugar for
(@heq (Type U) (Type U) (∀ x : A, B x) (∀ x : A', B' x))
Even if A, A', B, B' live in a much smaller universe.
As I described above, it doesn't seem to be a way to move this equality back to a smaller universe.
So, if we wanted to keep the heterogeneous equality as a constant, it seems we would
have to support axiom schemas. That is, hpiext would be parametrized by the universes where
A, A', B and B'. Another possibility would be to have universe polymorphism like Agda.
None of the solutions seem attractive.
So, we decided to have heterogeneous equality as a special kind of expression.
And use the trick above to move equalities back to the right universe.
BTW, the parser is not creating the new heterogeneous equalities yet.
Moreover, kernel.lean still contains a constant name heq2 that is the heterogeneous
equality as a constant.
Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
2014-02-07 18:07:08 +00:00
|
|
|
case expr_kind::HEq:
|
|
|
|
if (check_index(f, 0) && !visit(heq_lhs(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
if (check_index(f, 1) && !visit(heq_rhs(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
r = update_heq(e, rs(-2), rs(-1));
|
|
|
|
pop_rs(2);
|
|
|
|
break;
|
2014-02-04 00:52:49 +00:00
|
|
|
case expr_kind::Pair:
|
|
|
|
if (check_index(f, 0) && !visit(pair_first(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
if (check_index(f, 1) && !visit(pair_second(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
if (check_index(f, 2) && !visit(pair_type(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
r = update_pair(e, rs(-3), rs(-2), rs(-1));
|
|
|
|
pop_rs(3);
|
|
|
|
break;
|
|
|
|
case expr_kind::Proj:
|
|
|
|
if (check_index(f, 0) && !visit(proj_arg(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
r = update_proj(e, rs(-1));
|
|
|
|
pop_rs(1);
|
|
|
|
break;
|
2013-12-18 00:35:39 +00:00
|
|
|
case expr_kind::App: {
|
|
|
|
unsigned num = num_args(e);
|
|
|
|
while (f.m_index < num) {
|
|
|
|
expr const & c = arg(e, f.m_index);
|
|
|
|
f.m_index++;
|
|
|
|
if (!visit(c, offset))
|
|
|
|
goto begin_loop;
|
|
|
|
}
|
|
|
|
r = update_app(e, num, m_rs.end() - num_args(e));
|
|
|
|
pop_rs(num);
|
|
|
|
break;
|
|
|
|
}
|
2014-02-04 00:52:49 +00:00
|
|
|
case expr_kind::Sigma: case expr_kind::Pi: case expr_kind::Lambda:
|
2013-12-18 00:35:39 +00:00
|
|
|
if (check_index(f, 0) && !visit(abst_domain(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
if (check_index(f, 1) && !visit(abst_body(e), offset + 1))
|
|
|
|
goto begin_loop;
|
|
|
|
r = update_abstraction(e, rs(-2), rs(-1));
|
|
|
|
pop_rs(2);
|
|
|
|
break;
|
|
|
|
case expr_kind::Let:
|
|
|
|
if (check_index(f, 0) && let_type(e) && !visit(*let_type(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
if (check_index(f, 1) && !visit(let_value(e), offset))
|
|
|
|
goto begin_loop;
|
|
|
|
if (check_index(f, 2) && !visit(let_body(e), offset + 1))
|
|
|
|
goto begin_loop;
|
|
|
|
if (let_type(e)) {
|
|
|
|
r = update_let(e, some_expr(rs(-3)), rs(-2), rs(-1));
|
|
|
|
pop_rs(3);
|
|
|
|
} else {
|
|
|
|
r = update_let(e, none_expr(), rs(-2), rs(-1));
|
|
|
|
pop_rs(2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
save_result(e, r, offset, f.m_shared);
|
|
|
|
m_fs.pop_back();
|
|
|
|
}
|
|
|
|
lean_assert(m_rs.size() == 1);
|
|
|
|
r = m_rs.back();
|
|
|
|
m_rs.pop_back();
|
|
|
|
return r;
|
2013-07-24 07:32:01 +00:00
|
|
|
}
|
2013-11-20 21:17:58 +00:00
|
|
|
|
|
|
|
void clear() {
|
|
|
|
m_cache.clear();
|
2013-12-18 00:35:39 +00:00
|
|
|
m_fs.clear();
|
|
|
|
m_rs.clear();
|
2013-11-20 21:17:58 +00:00
|
|
|
}
|
2013-07-24 07:32:01 +00:00
|
|
|
};
|
2013-12-18 02:31:59 +00:00
|
|
|
|
|
|
|
template<typename F>
|
|
|
|
expr replace(expr const & e, F f) {
|
|
|
|
return replace_fn<F>(f)(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename F, typename P>
|
|
|
|
expr replace(expr const & e, F f, P p) {
|
|
|
|
return replace_fn<F, P>(f, p)(e);
|
|
|
|
}
|
2013-07-24 07:32:01 +00:00
|
|
|
}
|