/* Copyright (c) 2014 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #include #include #include "util/sstream.h" #include "util/optional.h" #include "kernel/instantiate.h" #include "kernel/type_checker.h" #include "kernel/default_converter.h" #include "library/annotation.h" #include "library/string.h" #include "library/explicit.h" #include "library/placeholder.h" #include "library/num.h" #include "library/constants.h" #include "library/projection.h" #include "library/kernel_serializer.h" #include "library/tactic/expr_to_tactic.h" namespace lean { static expr * g_and_then_tac_fn = nullptr; static expr * g_or_else_tac_fn = nullptr; static expr * g_id_tac_fn = nullptr; static expr * g_repeat_tac_fn = nullptr; static expr * g_determ_tac_fn = nullptr; static expr * g_tac_type = nullptr; static expr * g_builtin_tac = nullptr; static expr * g_fixpoint_tac = nullptr; expr const & get_and_then_tac_fn() { return *g_and_then_tac_fn; } expr const & get_or_else_tac_fn() { return *g_or_else_tac_fn; } expr const & get_id_tac_fn() { return *g_id_tac_fn; } expr const & get_determ_tac_fn() { return *g_determ_tac_fn; } expr const & get_repeat_tac_fn() { return *g_repeat_tac_fn; } expr const & get_tactic_type() { return *g_tac_type; } expr const & get_builtin_tac() { return *g_builtin_tac; } typedef std::unordered_map expr_to_tactic_map; static expr_to_tactic_map * g_map = nullptr; expr_to_tactic_map & get_expr_to_tactic_map() { return *g_map; } void register_tac(name const & n, expr_to_tactic_fn const & fn) { get_expr_to_tactic_map().insert(mk_pair(n, fn)); } bool has_tactic_decls(environment const & env) { try { type_checker tc(env); return tc.infer(*g_builtin_tac).first == *g_tac_type && tc.infer(*g_and_then_tac_fn).first == *g_tac_type >> (*g_tac_type >> *g_tac_type) && tc.infer(*g_or_else_tac_fn).first == *g_tac_type >> (*g_tac_type >> *g_tac_type) && tc.infer(*g_repeat_tac_fn).first == *g_tac_type >> *g_tac_type; } catch (exception &) { return false; } } static expr * g_tactic_expr_type = nullptr; static expr * g_tactic_expr_builtin = nullptr; static expr * g_tactic_expr_list_type = nullptr; static expr * g_tactic_identifier_type = nullptr; static expr * g_tactic_using_expr_type = nullptr; expr const & get_tactic_expr_type() { return *g_tactic_expr_type; } expr const & get_tactic_expr_builtin() { return *g_tactic_expr_builtin; } expr const & get_tactic_expr_list_type() { return *g_tactic_expr_list_type; } expr const & get_tactic_identifier_type() { return *g_tactic_identifier_type; } expr const & get_tactic_using_expr_type() { return *g_tactic_using_expr_type; } static std::string * g_tactic_expr_opcode = nullptr; static std::string * g_tactic_opcode = nullptr; std::string const & get_tactic_expr_opcode() { return *g_tactic_expr_opcode; } std::string const & get_tactic_opcode() { return *g_tactic_opcode; } class tactic_expr_macro_definition_cell : public macro_definition_cell { public: tactic_expr_macro_definition_cell() {} virtual name get_name() const { return get_tactic_expr_name(); } virtual pair check_type(expr const &, extension_context &, bool) const { return mk_pair(get_tactic_expr_type(), constraint_seq()); } virtual optional expand(expr const &, extension_context &) const { return some_expr(get_tactic_expr_builtin()); } virtual void write(serializer & s) const { s.write_string(get_tactic_expr_opcode()); } }; static macro_definition * g_tactic_expr_macro = nullptr; expr mk_tactic_expr(expr const & e) { return mk_macro(*g_tactic_expr_macro, 1, &e, e.get_tag()); } bool is_tactic_expr(expr const & e) { return is_macro(e) && macro_def(e).get_name() == get_tactic_expr_name(); } expr const & get_tactic_expr_expr(expr const & e) { lean_assert(is_tactic_expr(e)); expr const * it = &e; while (is_tactic_expr(*it)) it = ¯o_arg(*it, 0); return *it; } void check_tactic_expr(expr const & e, char const * error_msg) { if (!is_tactic_expr(e)) throw expr_to_tactic_exception(e, error_msg); } name const & tactic_expr_to_id(expr e, char const * error_msg) { if (is_tactic_expr(e)) e = get_tactic_expr_expr(e); if (is_constant(e)) { return const_name(e); } else if (is_local(e)) { return local_pp_name(e); } else if (is_as_atomic(e)) { e = get_app_fn(get_as_atomic_arg(e)); if (is_explicit(e)) e = get_explicit_arg(e); return tactic_expr_to_id(e, error_msg); } else { throw expr_to_tactic_exception(e, error_msg); } } static expr * g_expr_list_cons = nullptr; static expr * g_expr_list_nil = nullptr; expr mk_expr_list(unsigned num, expr const * args) { expr r = *g_expr_list_nil; unsigned i = num; while (i > 0) { --i; r = mk_app(*g_expr_list_cons, args[i], r); } return r; } expr ids_to_tactic_expr(buffer const & args) { buffer es; for (name const & id : args) { es.push_back(mk_local(id, mk_expr_placeholder())); } return mk_expr_list(es.size(), es.data()); } void get_tactic_expr_list_elements(expr l, buffer & r, char const * error_msg) { while (true) { if (l == *g_expr_list_nil) return; if (!is_app(l) || !is_app(app_fn(l)) || app_fn(app_fn(l)) != *g_expr_list_cons || !is_tactic_expr(app_arg(app_fn(l)))) throw expr_to_tactic_exception(l, error_msg); r.push_back(get_tactic_expr_expr(app_arg(app_fn(l)))); l = app_arg(l); } } void get_tactic_id_list_elements(expr l, buffer & r, char const * error_msg) { buffer es; get_tactic_expr_list_elements(l, es, error_msg); for (unsigned i = 0; i < es.size(); i++) { r.push_back(tactic_expr_to_id(es[i], error_msg)); } } static void throw_failed(expr const & e) { throw expr_to_tactic_exception(e, "failed to convert expression into tactic"); } /** \brief Return true if v is the constant tactic.builtin or the constant function that returns tactic.builtin_tactic */ static bool is_builtin_tactic(expr const & v) { if (is_lambda(v)) return is_builtin_tactic(binding_body(v)); else if (v == *g_builtin_tac) return true; else return false; } tactic expr_to_tactic(type_checker & tc, elaborate_fn const & fn, expr e, pos_info_provider const * p) { e = copy_tag(e, tc.whnf(e).first); expr f = get_app_fn(e); if (!is_constant(f)) throw_failed(e); optional it = tc.env().find(const_name(f)); if (!it || !it->is_definition()) throw_failed(e); expr v = it->get_value(); if (is_builtin_tactic(v)) { auto const & map = get_expr_to_tactic_map(); auto it2 = map.find(const_name(f)); if (it2 != map.end()) return it2->second(tc, fn, e, p); else throw expr_to_tactic_exception(e, sstream() << "implementation for builtin tactic '" << const_name(f) << "' was not found"); } else { // unfold definition buffer locals; get_app_rev_args(e, locals); level_param_names const & ps = it->get_univ_params(); levels ls = const_levels(f); unsigned num_ps = length(ps); unsigned num_ls = length(ls); if (num_ls > num_ps) throw expr_to_tactic_exception(e, sstream() << "invalid number of universes"); if (num_ls < num_ps) { buffer extra_ls; name_generator ngen = tc.mk_ngen(); for (unsigned i = num_ls; i < num_ps; i++) extra_ls.push_back(mk_meta_univ(ngen.next())); ls = append(ls, to_list(extra_ls.begin(), extra_ls.end())); } v = instantiate_univ_params(v, ps, ls); v = apply_beta(v, locals.size(), locals.data()); return expr_to_tactic(tc, fn, v, p); } } static name * g_tmp_prefix = nullptr; LEAN_THREAD_VALUE(unsigned, g_expr_tac_id, 0); static name_generator next_name_generator() { unsigned r = g_expr_tac_id; g_expr_tac_id++; return name_generator(name(*g_tmp_prefix, r)); } unsigned get_unsigned(type_checker & tc, expr const & e, expr const & ref) { optional k = to_num(e); if (!k) k = to_num(tc.whnf(e).first); if (!k) throw expr_to_tactic_exception(ref, "invalid tactic, second argument must be a numeral"); if (!k->is_unsigned_int()) throw expr_to_tactic_exception(ref, "invalid tactic, second argument does not fit in " "a machine unsigned integer"); return k->get_unsigned_int(); } unsigned get_unsigned_arg(type_checker & tc, expr const & e, unsigned i) { buffer args; get_app_args(e, args); if (i >= args.size()) throw expr_to_tactic_exception(e, "invalid tactic, insufficient number of arguments"); return get_unsigned(tc, args[i], e); } optional get_optional_unsigned(type_checker & tc, expr const & e) { if (is_app(e) && is_constant(get_app_fn(e))) { if (const_name(get_app_fn(e)) == get_option_some_name()) { return optional(get_unsigned(tc, app_arg(e), e)); } else if (const_name(get_app_fn(e)) == get_option_none_name()) { return optional(); } } throw expr_to_tactic_exception(e, "invalid tactic, argument is not an option num"); } class tac_builtin_opaque_converter : public projection_converter { public: tac_builtin_opaque_converter(environment const & env):projection_converter(env) {} virtual bool is_opaque(declaration const & d) const { name n = d.get_name(); if (!is_prefix_of(get_tactic_name(), n)) return projection_converter::is_opaque(d); expr v = d.get_value(); while (is_lambda(v)) v = binding_body(v); if (is_constant(v) && const_name(v) == get_tactic_builtin_name()) return true; return projection_converter::is_opaque(d); } }; tactic expr_to_tactic(environment const & env, elaborate_fn const & fn, expr const & e, pos_info_provider const * p) { bool memoize = false; type_checker tc(env, next_name_generator(), std::unique_ptr(new tac_builtin_opaque_converter(env)), memoize); return expr_to_tactic(tc, fn, e, p); } tactic fixpoint(expr const & b, elaborate_fn const & fn) { return tactic([=](environment const & env, io_state const & ios, proof_state const & s) -> proof_state_seq { return expr_to_tactic(env, fn, b, nullptr)(env, ios, s); }); } void register_simple_tac(name const & n, std::function f) { register_tac(n, [=](type_checker &, elaborate_fn const &, expr const & e, pos_info_provider const *) { if (!is_constant(e)) throw expr_to_tactic_exception(e, "invalid constant tactic"); return f(); }); } void register_bin_tac(name const & n, std::function f) { register_tac(n, [=](type_checker & tc, elaborate_fn const & fn, expr const & e, pos_info_provider const * p) { buffer args; get_app_args(e, args); if (args.size() != 2) throw expr_to_tactic_exception(e, "invalid binary tactic, it must have two arguments"); tactic t1 = expr_to_tactic(tc, fn, args[0], p); tactic t2 = expr_to_tactic(tc, fn, args[1], p); return f(t1, t2); }); } void register_unary_tac(name const & n, std::function f) { register_tac(n, [=](type_checker & tc, elaborate_fn const & fn, expr const & e, pos_info_provider const * p) { buffer args; get_app_args(e, args); if (args.size() != 1) throw expr_to_tactic_exception(e, "invalid unary tactic, it must have one argument"); return f(expr_to_tactic(tc, fn, args[0], p)); }); } void register_unary_num_tac(name const & n, std::function f) { register_tac(n, [=](type_checker & tc, elaborate_fn const & fn, expr const & e, pos_info_provider const * p) { buffer args; get_app_args(e, args); if (args.size() != 2) throw expr_to_tactic_exception(e, "invalid tactic, it must have two arguments"); tactic t = expr_to_tactic(tc, fn, args[0], p); return f(t, get_unsigned_arg(tc, e, 1)); }); } void register_num_tac(name const & n, std::function f) { register_tac(n, [=](type_checker & tc, elaborate_fn const &, expr const & e, pos_info_provider const *) { buffer args; get_app_args(e, args); if (args.size() != 1) throw expr_to_tactic_exception(e, "invalid tactic, it must have one argument"); return f(get_unsigned_arg(tc, e, 0)); }); } static name * g_by_name = nullptr; static name * g_by_plus_name = nullptr; expr mk_by(expr const & e) { return mk_annotation(*g_by_name, e); } bool is_by(expr const & e) { return is_annotation(e, *g_by_name); } expr const & get_by_arg(expr const & e) { lean_assert(is_by(e)); return get_annotation_arg(e); } expr mk_by_plus(expr const & e) { return mk_annotation(*g_by_plus_name, e); } bool is_by_plus(expr const & e) { return is_annotation(e, *g_by_plus_name); } expr const & get_by_plus_arg(expr const & e) { lean_assert(is_by_plus(e)); return get_annotation_arg(e); } void initialize_expr_to_tactic() { g_tmp_prefix = new name(name::mk_internal_unique_name()); g_by_name = new name("by"); register_annotation(*g_by_name); g_by_plus_name = new name("by+"); register_annotation(*g_by_plus_name); g_map = new expr_to_tactic_map(); g_tactic_expr_type = new expr(mk_constant(get_tactic_expr_name())); g_tactic_expr_builtin = new expr(mk_constant(get_tactic_expr_builtin_name())); g_tactic_expr_list_type = new expr(mk_constant(get_tactic_expr_list_name())); g_tactic_identifier_type = new expr(mk_constant(get_tactic_identifier_name())); g_tactic_using_expr_type = new expr(mk_constant(get_tactic_using_expr_name())); g_tactic_expr_opcode = new std::string("TACE"); g_tactic_expr_macro = new macro_definition(new tactic_expr_macro_definition_cell()); register_macro_deserializer(*g_tactic_expr_opcode, [](deserializer &, unsigned num, expr const * args) { if (num != 1) throw corrupted_stream_exception(); return mk_tactic_expr(args[0]); }); g_expr_list_cons = new expr(mk_constant(get_tactic_expr_list_cons_name())); g_expr_list_nil = new expr(mk_constant(get_tactic_expr_list_nil_name())); g_and_then_tac_fn = new expr(Const(get_tactic_and_then_name())); g_or_else_tac_fn = new expr(Const(get_tactic_or_else_name())); g_id_tac_fn = new expr(Const(get_tactic_id_name())); g_repeat_tac_fn = new expr(Const(get_tactic_repeat_name())); g_determ_tac_fn = new expr(Const(get_tactic_determ_name())); g_tac_type = new expr(Const(get_tactic_name())); g_builtin_tac = new expr(Const(get_tactic_builtin_name())); g_fixpoint_tac = new expr(Const(get_tactic_fixpoint_name())); register_simple_tac(get_tactic_id_name(), []() { return id_tactic(); }); register_simple_tac(get_tactic_now_name(), []() { return now_tactic(); }); register_simple_tac(get_tactic_fail_name(), []() { return fail_tactic(); }); register_simple_tac(get_tactic_beta_name(), []() { return beta_tactic(); }); register_bin_tac(get_tactic_and_then_name(), [](tactic const & t1, tactic const & t2) { return then(t1, t2); }); register_bin_tac(get_tactic_append_name(), [](tactic const & t1, tactic const & t2) { return append(t1, t2); }); register_bin_tac(get_tactic_interleave_name(), [](tactic const & t1, tactic const & t2) { return interleave(t1, t2); }); register_bin_tac(get_tactic_par_name(), [](tactic const & t1, tactic const & t2) { return par(t1, t2); }); register_bin_tac(get_tactic_or_else_name(), [](tactic const & t1, tactic const & t2) { return orelse(t1, t2); }); register_unary_tac(get_tactic_repeat_name(), [](tactic const & t1) { return repeat(t1); }); register_unary_tac(get_tactic_all_goals_name(), [](tactic const & t1) { return all_goals(t1); }); register_unary_num_tac(get_tactic_at_most_name(), [](tactic const & t, unsigned k) { return take(t, k); }); register_unary_num_tac(get_tactic_discard_name(), [](tactic const & t, unsigned k) { return discard(t, k); }); register_unary_num_tac(get_tactic_focus_at_name(), [](tactic const & t, unsigned k) { return focus(t, k); }); register_unary_num_tac(get_tactic_try_for_name(), [](tactic const & t, unsigned k) { return try_for(t, k); }); register_num_tac(get_tactic_rotate_left_name(), [](unsigned k) { return rotate_left(k); }); register_num_tac(get_tactic_rotate_right_name(), [](unsigned k) { return rotate_right(k); }); register_tac(get_tactic_fixpoint_name(), [](type_checker & tc, elaborate_fn const & fn, expr const & e, pos_info_provider const *) { if (!is_constant(app_fn(e))) throw expr_to_tactic_exception(e, "invalid fixpoint tactic, it must have one argument"); expr r = tc.whnf(mk_app(app_arg(e), e)).first; return fixpoint(r, fn); }); } void finalize_expr_to_tactic() { delete g_expr_list_cons; delete g_expr_list_nil; delete g_tactic_expr_type; delete g_tactic_expr_builtin; delete g_tactic_expr_list_type; delete g_tactic_identifier_type; delete g_tactic_using_expr_type; delete g_tactic_expr_opcode; delete g_tactic_expr_macro; delete g_fixpoint_tac; delete g_builtin_tac; delete g_tac_type; delete g_determ_tac_fn; delete g_repeat_tac_fn; delete g_or_else_tac_fn; delete g_and_then_tac_fn; delete g_id_tac_fn; delete g_map; delete g_tactic_opcode; delete g_by_name; delete g_by_plus_name; delete g_tmp_prefix; } }