/* 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 #include #include #include "library/sorry.h" #include "util/flet.h" #include "util/fresh_name.h" #include "kernel/replace_fn.h" #include "kernel/free_vars.h" #include "kernel/abstract.h" #include "kernel/instantiate.h" #include "library/annotation.h" #include "library/aliases.h" #include "library/class.h" #include "library/scoped_ext.h" #include "library/expr_pair.h" #include "library/placeholder.h" #include "library/private.h" #include "library/protected.h" #include "library/quote.h" #include "library/explicit.h" #include "library/num.h" #include "library/util.h" #include "library/print.h" #include "library/pp_options.h" #include "library/delayed_abstraction.h" #include "library/constants.h" #include "library/replace_visitor.h" #include "library/string.h" #include "library/type_context.h" #include "library/idx_metavar.h" #include "library/equations_compiler/equations.h" #include "library/tactic/tactic_state.h" #include "library/compiler/comp_irrelevant.h" #include "library/compiler/erase_irrelevant.h" #include "library/compiler/rec_fn_macro.h" #include "frontends/lean/pp.h" #include "frontends/lean/util.h" #include "frontends/lean/prenum.h" #include "frontends/lean/token_table.h" #include "frontends/lean/builtin_exprs.h" #include "frontends/lean/structure_cmd.h" #include "frontends/lean/parser_config.h" #include "frontends/lean/scanner.h" #include "frontends/lean/tokens.h" #include "frontends/lean/typed_expr.h" #include "library/trace.h" namespace lean { static format * g_ellipsis_n_fmt = nullptr; static format * g_ellipsis_fmt = nullptr; static format * g_placeholder_fmt = nullptr; static format * g_lambda_n_fmt = nullptr; static format * g_lambda_fmt = nullptr; static format * g_forall_n_fmt = nullptr; static format * g_forall_fmt = nullptr; static format * g_pi_n_fmt = nullptr; static format * g_pi_fmt = nullptr; static format * g_arrow_n_fmt = nullptr; static format * g_arrow_fmt = nullptr; static format * g_let_fmt = nullptr; static format * g_in_fmt = nullptr; static format * g_assign_fmt = nullptr; static format * g_have_fmt = nullptr; static format * g_from_fmt = nullptr; static format * g_visible_fmt = nullptr; static format * g_show_fmt = nullptr; static format * g_explicit_fmt = nullptr; static format * g_partial_explicit_fmt = nullptr; class nat_numeral_pp { name m_nat; expr m_nat_zero; expr m_nat_succ; public: nat_numeral_pp(): m_nat(get_nat_name()), m_nat_zero(mk_constant(get_nat_zero_name())), m_nat_succ(mk_constant(get_nat_succ_name())) { } // Return an unsigned if \c e if of the form (succ^k zero), and k // fits in a machine unsigned integer. optional to_unsigned(expr const & e) const { unsigned r = 0; expr const * it = &e; while (true) { if (*it == m_nat_zero) { return optional(r); } else if (is_app(*it) && app_fn(*it) == m_nat_succ) { if (r == std::numeric_limits::max()) return optional(); // just in case, it does not really happen in practice r++; it = &app_arg(*it); } else if (is_zero(get_app_fn(*it))) { return optional(r); } else { return optional(); } } } }; static nat_numeral_pp * g_nat_numeral_pp = nullptr; static optional to_unsigned(expr const & e) { return g_nat_numeral_pp->to_unsigned(e); } static name * g_pp_using_anonymous_constructor = nullptr; bool pp_using_anonymous_constructor(environment const & env, name const & n) { return has_attribute(env, *g_pp_using_anonymous_constructor, n); } void initialize_pp() { g_ellipsis_n_fmt = new format(highlight(format("…"))); g_ellipsis_fmt = new format(highlight(format("..."))); g_placeholder_fmt = new format(highlight(format("_"))); g_lambda_n_fmt = new format(highlight_keyword(format("λ"))); g_lambda_fmt = new format(highlight_keyword(format("fun"))); g_forall_n_fmt = new format(highlight_keyword(format("∀"))); g_forall_fmt = new format(highlight_keyword(format("forall"))); g_pi_n_fmt = new format(highlight_keyword(format("Π"))); g_pi_fmt = new format(highlight_keyword(format("Pi"))); g_arrow_n_fmt = new format(highlight_keyword(format("→"))); g_arrow_fmt = new format(highlight_keyword(format("->"))); g_let_fmt = new format(highlight_keyword(format("let"))); g_in_fmt = new format(highlight_keyword(format("in"))); g_assign_fmt = new format(highlight_keyword(format(":="))); g_have_fmt = new format(highlight_keyword(format("have"))); g_from_fmt = new format(highlight_keyword(format("from"))); g_visible_fmt = new format(highlight_keyword(format("[visible]"))); g_show_fmt = new format(highlight_keyword(format("show"))); g_explicit_fmt = new format(highlight_keyword(format("@"))); g_partial_explicit_fmt = new format(highlight_keyword(format("@@"))); g_nat_numeral_pp = new nat_numeral_pp(); g_pp_using_anonymous_constructor = new name("pp_using_anonymous_constructor"); register_system_attribute(basic_attribute::with_check( *g_pp_using_anonymous_constructor, "if a structure S is marked with this attribute, then its constructor applications" "are pretty printed using the anonymous constructor", [](environment const & env, name const & d, bool) -> void { if (!is_structure(env, d)) throw exception( "invalid 'pp_using_anonymous_constructor' use, " "only structures can be marked with this attribute"); })); } void finalize_pp() { delete g_pp_using_anonymous_constructor; delete g_nat_numeral_pp; delete g_ellipsis_n_fmt; delete g_ellipsis_fmt; delete g_placeholder_fmt; delete g_lambda_n_fmt; delete g_lambda_fmt; delete g_forall_n_fmt; delete g_forall_fmt; delete g_pi_n_fmt; delete g_pi_fmt; delete g_arrow_n_fmt; delete g_arrow_fmt; delete g_let_fmt; delete g_in_fmt; delete g_assign_fmt; delete g_have_fmt; delete g_from_fmt; delete g_visible_fmt; delete g_show_fmt; delete g_partial_explicit_fmt; delete g_explicit_fmt; } /** \brief We assume a metavariable name has a suggestion embedded in it WHEN its last component is a string. */ static bool has_embedded_suggestion(name const & m) { return m.is_string(); } /** \see extract_suggestion */ static name extract_suggestion_core(name const & m) { if (m.is_string()) { if (m.is_atomic()) return m; else return name(extract_suggestion_core(m.get_prefix()), m.get_string()); } else { return name(); } } /** \brief Extract "suggested name" embedded in a metavariable name \pre has_embedded_suggestion(m) */ static name extract_suggestion(name const & m) { lean_assert(has_embedded_suggestion(m)); name r = extract_suggestion_core(m); lean_assert(!r.is_anonymous()); return r; } name pretty_fn::mk_metavar_name(name const & m, optional const & prefix) { if (auto it = m_purify_meta_table.find(m)) return *it; if (has_embedded_suggestion(m)) { name suggested = extract_suggestion(m); name r = suggested; unsigned i = 1; while (m_purify_used_metas.contains(r)) { r = suggested.append_after(i); i++; } m_purify_used_metas.insert(r); m_purify_meta_table.insert(m, r); return r; } else { name new_m; if (prefix) new_m = prefix->append_after(m_next_meta_idx); else new_m = m_meta_prefix.append_after(m_next_meta_idx); m_next_meta_idx++; m_purify_meta_table.insert(m, new_m); return new_m; } } name pretty_fn::mk_local_name(name const & n, name const & suggested) { if (!m_purify_locals) return suggested; if (auto it = m_purify_local_table.find(n)) return *it; unsigned i = 1; name r = suggested; while (m_purify_used_locals.contains(r)) { r = suggested.append_after(i); i++; } m_purify_used_locals.insert(r); m_purify_local_table.insert(n, r); return r; } level pretty_fn::purify(level const & l) { if (!m_universes || !m_purify_metavars || !has_meta(l)) return l; return replace(l, [&](level const & l) { if (!has_meta(l)) return some_level(l); if (is_metavar_decl_ref(l)) return some_level(mk_meta_univ(mk_metavar_name(meta_id(l), "l"))); if (is_meta(l) && !is_idx_metauniv(l)) return some_level(mk_meta_univ(mk_metavar_name(meta_id(l)))); return none_level(); }); } expr pretty_fn::infer_type(expr const & e) { try { return m_ctx.infer(e); } catch (exception &) { expr dummy = mk_Prop(); return dummy; } } /** \brief Make sure that all metavariables have reasonable names, and for all local constants l1 l2, mlocal_pp_name(l1) != mlocal_pp_name(l2). \remark pretty_fn will create new local constants when pretty printing, but it will make sure that the new constants will not produce collisions. \remark We do not purify metavariable names IF the user provided them. We use the test (mlocal_name(e) == mlocal_pp_name(e)) to decide whether the user provided the name or not. */ expr pretty_fn::purify(expr const & e) { if (!has_expr_metavar(e) && !has_local(e) && (!m_universes || !has_univ_metavar(e))) return e; return replace(e, [&](expr const & e, unsigned) { if (!has_expr_metavar(e) && !has_local(e) && (!m_universes || !has_univ_metavar(e))) { return some_expr(e); } else if (m_purify_metavars && is_metavar_decl_ref(e) && mlocal_name(e) == mlocal_pp_name(e)) { return some_expr(mk_metavar(mk_metavar_name(mlocal_name(e), "m"), infer_type(e))); } else if (m_purify_metavars && is_metavar(e) && mlocal_name(e) == mlocal_pp_name(e) && !is_idx_metavar(e)) { return some_expr(mk_metavar(mk_metavar_name(mlocal_name(e)), infer_type(e))); } else if (is_local(e)) { return some_expr(mk_local(mlocal_name(e), mk_local_name(mlocal_name(e), mlocal_pp_name(e)), infer_type(e), local_info(e))); } else if (is_constant(e)) { return some_expr(update_constant(e, map(const_levels(e), [&](level const & l) { return purify(l); }))); } else if (is_sort(e)) { return some_expr(update_sort(e, purify(sort_level(e)))); } else { return none_expr(); } }); } void pretty_fn::set_options_core(options const & _o) { options o = _o; bool all = get_pp_all(o); if (all) { o = o.update_if_undef(get_pp_implicit_name(), true); o = o.update_if_undef(get_pp_proofs_name(), true); o = o.update_if_undef(get_pp_coercions_name(), true); o = o.update_if_undef(get_pp_notation_name(), false); o = o.update_if_undef(get_pp_universes_name(), true); o = o.update_if_undef(get_pp_full_names_name(), true); o = o.update_if_undef(get_pp_beta_name(), false); o = o.update_if_undef(get_pp_numerals_name(), false); o = o.update_if_undef(get_pp_strings_name(), false); o = o.update_if_undef(get_pp_binder_types_name(), true); } m_options = o; m_indent = get_pp_indent(o); m_max_depth = get_pp_max_depth(o); m_max_steps = get_pp_max_steps(o); m_implict = get_pp_implicit(o); m_proofs = get_pp_proofs(o); m_unicode = get_pp_unicode(o); m_coercion = get_pp_coercions(o); m_notation = get_pp_notation(o); m_universes = get_pp_universes(o); m_full_names = get_pp_full_names(o); m_private_names = get_pp_private_names(o); m_purify_metavars = get_pp_purify_metavars(o); m_purify_locals = get_pp_purify_locals(o); m_locals_full_names = get_pp_locals_full_names(o); m_beta = get_pp_beta(o); m_numerals = get_pp_numerals(o); m_strings = get_pp_strings(o); m_preterm = get_pp_preterm(o); m_binder_types = get_pp_binder_types(o); m_hide_comp_irrel = get_pp_hide_comp_irrel(o); m_delayed_abstraction = get_pp_delayed_abstraction(o); m_use_holes = get_pp_use_holes(o); m_annotations = get_pp_annotations(o); m_hide_full_terms = get_formatter_hide_full_terms(o); m_num_nat_coe = m_numerals; m_structure_instances = get_pp_structure_instances(o); m_structure_instances_qualifier = get_pp_structure_instances_qualifier(o); m_structure_projections = get_pp_structure_projections(o); } void pretty_fn::set_options(options const & o) { if (is_eqp(o, m_options)) return; set_options_core(o); } format pretty_fn::pp_child(level const & l) { if (is_explicit(l) || is_param(l) || is_meta(l)) { return pp_level(l); } else { return paren(pp_level(l)); } } format pretty_fn::pp_max(level l) { lean_assert(is_max(l) || is_imax(l)); format r = format(is_max(l) ? "max" : "imax"); level lhs = is_max(l) ? max_lhs(l) : imax_lhs(l); level rhs = is_max(l) ? max_rhs(l) : imax_rhs(l); r += nest(m_indent, compose(line(), pp_child(lhs))); while (kind(rhs) == kind(l)) { l = rhs; lhs = is_max(l) ? max_lhs(l) : imax_lhs(l); rhs = is_max(l) ? max_rhs(l) : imax_rhs(l); r += nest(m_indent, compose(line(), pp_child(lhs))); } r += nest(m_indent, compose(line(), pp_child(rhs))); return group(r); } format pretty_fn::pp_meta(level const & l) { if (m_universes) { if (is_idx_metauniv(l)) { return format((sstream() << "?u_" << to_meta_idx(l)).str()); } else if (is_metavar_decl_ref(l)) { return format((sstream() << "?l_" << get_metavar_decl_ref_suffix(l)).str()); } else { return compose(format("?"), format(meta_id(l))); } } else { return format("?"); } } format pretty_fn::pp_level(level const & l) { if (is_explicit(l)) { lean_assert(get_depth(l) > 0); return format(get_depth(l) - 1); } else { switch (kind(l)) { case level_kind::Zero: lean_unreachable(); // LCOV_EXCL_LINE case level_kind::Param: return format(param_id(l)); case level_kind::Meta: return pp_meta(l); case level_kind::Succ: { auto p = to_offset(l); return pp_child(p.first) + format("+") + format(p.second); } case level_kind::Max: case level_kind::IMax: return pp_max(l); } lean_unreachable(); // LCOV_EXCL_LINE } } bool pretty_fn::is_implicit(expr const & f) { // Remark: we assume preterms do not have implicit arguments, // since they have not been elaborated yet. // Moreover, the type checker will probably produce an error // when trying to infer type information if (m_implict || m_preterm) return false; // showing implicit arguments if (!closed(f)) { // the Lean type checker assumes expressions are closed. return false; } try { expr t = m_ctx.relaxed_whnf(m_ctx.infer(f)); if (is_pi(t)) { binder_info bi = binding_info(t); return bi.is_implicit() || bi.is_strict_implicit() || bi.is_inst_implicit(); } else { return false; } } catch (exception &) { return false; } } bool pretty_fn::is_default_arg_app(expr const & e) { if (m_implict || m_preterm) return false; // showing default arguments if (!closed(app_fn(e))) { // the Lean type checker assumes expressions are closed. return false; } try { expr t = m_ctx.relaxed_whnf(m_ctx.infer(app_fn(e))); if (is_pi(t)) { expr arg_type = binding_domain(t); t = binding_body(t); if (!is_pi(t) && !is_var(t) && is_app_of(arg_type, get_opt_param_name(), 2)) { expr defval = app_arg(arg_type); return closed(defval) && defval == app_arg(e); } } } catch (exception &) { } return false; } bool pretty_fn::is_prop(expr const & e) { try { expr t = m_ctx.relaxed_whnf(m_ctx.infer(e)); return t == mk_Prop(); } catch (exception &) { return false; } } auto pretty_fn::add_paren_if_needed(result const & r, unsigned bp) -> result { if (r.rbp() < bp) { return result(paren(r.fmt())); } else { return r; } } auto pretty_fn::pp_child_core(expr const & e, unsigned bp, bool ignore_hide) -> result { return add_paren_if_needed(pp(e, ignore_hide), bp); } static expr consume_ref_annotations(expr const & e) { if (is_explicit(e)) return consume_ref_annotations(get_explicit_arg(e)); else return e; } enum local_ref_kind { LocalRef, OverridenLocalRef, NotLocalRef }; static local_ref_kind check_local_ref(environment const & env, expr const & e, unsigned & num_ref_univ_params) { expr const & rfn = get_app_fn(e); if (!is_constant(rfn)) return NotLocalRef; auto ref = get_local_ref(env, const_name(rfn)); if (!ref) return NotLocalRef; if (is_as_atomic(*ref)) ref = get_as_atomic_arg(*ref); buffer ref_args, e_args; expr ref_fn = consume_ref_annotations(get_app_args(*ref, ref_args)); get_app_args(e, e_args); if (!is_constant(ref_fn) || e_args.size() != ref_args.size()) { return NotLocalRef; } for (unsigned i = 0; i < e_args.size(); i++) { expr e_arg = e_args[i]; expr ref_arg = consume_ref_annotations(ref_args[i]); if (!is_local(e_arg) || !is_local(ref_arg) || mlocal_pp_name(e_arg) != mlocal_pp_name(ref_arg)) return OverridenLocalRef; } num_ref_univ_params = length(const_levels(ref_fn)); buffer lvls; to_buffer(const_levels(rfn), lvls); if (lvls.size() < num_ref_univ_params) { return NotLocalRef; } for (unsigned i = 0; i < num_ref_univ_params; i++) { level const & l = lvls[i]; if (!is_param(l)) { return OverridenLocalRef; } for (unsigned j = 0; j < i; j++) if (lvls[i] == lvls[j]) { return OverridenLocalRef; } } return LocalRef; } static bool is_local_ref(environment const & env, expr const & e) { unsigned num_ref_univ_params; return check_local_ref(env, e, num_ref_univ_params) == LocalRef; } auto pretty_fn::pp_overriden_local_ref(expr const & e) -> result { buffer args; expr const & fn = get_app_args(e, args); result res_fn; { flet set1(m_full_names, true); res_fn = pp_const(fn, some(0u)); } format fn_fmt = res_fn.fmt(); if (const_name(fn).is_atomic()) fn_fmt = compose(format("_root_."), fn_fmt); if (m_implict && has_implicit_args(fn)) fn_fmt = compose(*g_explicit_fmt, fn_fmt); format r_fmt = fn_fmt; expr curr_fn = fn; for (unsigned i = 0; i < args.size(); i++) { expr const & arg = args[i]; if (m_implict || !is_implicit(curr_fn)) { result res_arg = pp_child(arg, max_bp()); r_fmt = group(compose(r_fmt, nest(m_indent, compose(line(), res_arg.fmt())))); } curr_fn = mk_app(curr_fn, arg); } return result(max_bp()-1, r_fmt); } // Return some result if \c e is of the form (c p_1 ... p_n) where // c is a constant, and p_i's are parameters fixed in a section. auto pretty_fn::pp_local_ref(expr const & e) -> optional { unsigned num_ref_univ_params; switch (check_local_ref(m_env, e, num_ref_univ_params)) { case NotLocalRef: return optional(); case LocalRef: return some(pp_const(get_app_fn(e), optional(num_ref_univ_params))); case OverridenLocalRef: return some(pp_overriden_local_ref(e)); } lean_unreachable(); } static bool is_coercion(expr const & e) { return is_app_of(e, get_coe_name()) && get_app_num_args(e) >= 4; } static bool is_coercion_fn(expr const & e) { return is_app_of(e, get_coe_fn_name()) && get_app_num_args(e) >= 3; } auto pretty_fn::pp_hide_coercion(expr const & e, unsigned bp, bool ignore_hide) -> result { lean_assert(is_coercion(e)); buffer args; get_app_args(e, args); if (args.size() == 4) { return pp_child(args[3], bp, ignore_hide); } else { expr new_e = mk_app(args.size() - 3, args.data() + 3); return pp_child(new_e, bp, ignore_hide); } } auto pretty_fn::pp_hide_coercion_fn(expr const & e, unsigned bp, bool ignore_hide) -> result { lean_assert(is_coercion_fn(e)); buffer args; get_app_args(e, args); if (args.size() == 3) { return pp_child(args[2], bp, ignore_hide); } else { expr new_e = mk_app(args.size() - 2, args.data() + 2); return pp_child(new_e, bp, ignore_hide); } } auto pretty_fn::pp_child(expr const & e, unsigned bp, bool ignore_hide) -> result { if (is_app(e)) { if (auto r = pp_local_ref(e)) return add_paren_if_needed(*r, bp); if (m_numerals) { if (auto n = to_num(e)) return pp_num(*n, bp); } if (m_strings) { if (auto r = to_string(e)) return pp_string_literal(*r); if (auto r = to_char(m_ctx, e)) return pp_char_literal(*r); } expr const & f = app_fn(e); if (is_implicit(f)) { return pp_child(f, bp, ignore_hide); } else if (!m_coercion && is_coercion(e)) { return pp_hide_coercion(e, bp, ignore_hide); } else if (!m_coercion && is_coercion_fn(e)) { return pp_hide_coercion_fn(e, bp, ignore_hide); } } return pp_child_core(e, bp, ignore_hide); } auto pretty_fn::pp_var(expr const & e) -> result { unsigned vidx = var_idx(e); return result(compose(format("#"), format(vidx))); } auto pretty_fn::pp_sort(expr const & e) -> result { level u = sort_level(e); if (u == mk_level_zero()) { return result(format("Prop")); } else if (u == mk_level_one()) { return result(format("Type")); } else if (optional u1 = dec_level(u)) { return result(max_bp()-1, group(format("Type") + space() + nest(5, pp_child(*u1)))); } else { return result(max_bp()-1, group(format("Sort") + space() + nest(5, pp_child(u)))); } } optional pretty_fn::is_aliased(name const & n) const { if (auto it = is_expr_aliased(m_env, n)) { // must check if we are not shadow by current namespace for (name const & ns : get_namespaces(m_env)) { if (!ns.is_anonymous() && m_env.find(ns + *it)) return optional(); } return it; } else { return optional(); } } format pretty_fn::escape(name const & n) { // also escape keyword-like names if (n.is_atomic() && n.is_string() && find(m_token_table, n.get_string())) { sstream ss; ss << "«" << n.get_string() << "»"; return format(ss.str()); } return format(n.escape()); } auto pretty_fn::pp_const(expr const & e, optional const & num_ref_univ_params) -> result { if (is_neutral_expr(e) && m_unicode) return format("◾"); if (is_unreachable_expr(e) && m_unicode) return format("⊥"); name n = const_name(e); if (m_notation && n == get_unit_star_name()) return format("()"); if (!num_ref_univ_params) { if (auto r = pp_local_ref(e)) return *r; } // Remark: if num_ref_univ_params is "some", then it contains the number of // universe levels that are fixed in a section. That is, \c e corresponds to // a constant in a section which has fixed levels. auto short_n = n; if (!m_full_names) { if (auto it = is_aliased(n)) { if (!m_private_names || !hidden_to_user_name(m_env, n)) short_n = *it; } else { for (name const & ns : get_namespaces(m_env)) { if (!ns.is_anonymous()) { name new_n = n.replace_prefix(ns, name()); if (new_n != n && !new_n.is_anonymous() && (!new_n.is_atomic() || !is_protected(m_env, n))) { short_n = new_n; break; } } } } } if (!m_private_names) { if (auto n1 = hidden_to_user_name(m_env, short_n)) short_n = *n1; } if (m_ctx.has_local_pp_name(short_n.get_root())) { if (m_ctx.has_local_pp_name(n.get_root())) { n = get_root_tk() + n; } } else { n = short_n; } if (m_universes && !empty(const_levels(e))) { unsigned first_idx = 0; buffer ls; to_buffer(const_levels(e), ls); if (num_ref_univ_params) { if (ls.size() <= *num_ref_univ_params) return result(escape(n)); else first_idx = *num_ref_univ_params; } format r = compose(escape(n), format(".{")); bool first = true; for (unsigned i = first_idx; i < ls.size(); i++) { level const & l = ls[i]; format l_fmt = pp_level(l); if (is_max(l) || is_imax(l)) l_fmt = paren(l_fmt); if (first) r += nest(m_indent, l_fmt); else r += nest(m_indent, compose(line(), l_fmt)); first = false; } r += format("}"); return result(group(r)); } else { return result(escape(n)); } } static format pp_hole() { return format("{! !}"); } auto pretty_fn::pp_meta(expr const & e) -> result { if (m_use_holes) { return pp_hole(); } else if (mlocal_name(e) != mlocal_pp_name(e)) { return result(format(mlocal_pp_name(e))); } else if (is_idx_metavar(e)) { return result(format((sstream() << "?x_" << to_meta_idx(e)).str())); } else if (is_metavar_decl_ref(e) && !m_purify_metavars) { return result(format((sstream() << "?m_" << get_metavar_decl_ref_suffix(e)).str())); } else if (m_purify_metavars) { return result(compose(format("?"), format(mlocal_name(e)))); } else { return result(compose(format("?M."), format(mlocal_name(e)))); } } auto pretty_fn::pp_local(expr const & e) -> result { name n = sanitize_if_fresh(mlocal_pp_name(e)); n = sanitize_name_generator_name(n); if (m_locals_full_names) return result(format("<") + format(n + mlocal_name(e)) + format(">")); else return format(escape(n)); } bool pretty_fn::has_implicit_args(expr const & f) { if (!closed(f) || m_preterm) { // The Lean type checker assumes expressions are closed. // If preterms are being processed, then we assume // there are no implicit arguments. return false; } try { expr type = m_ctx.relaxed_whnf(m_ctx.infer(f)); push_local_fn push_local(m_ctx); while (is_pi(type)) { binder_info bi = binding_info(type); if (bi.is_implicit() || bi.is_strict_implicit() || bi.is_inst_implicit()) return true; expr local = push_local(binding_name(type), binding_domain(type), binding_info(type)); type = m_ctx.relaxed_whnf(instantiate(binding_body(type), local)); } return false; } catch (exception &) { return false; } } static bool is_structure_instance(environment const & env, expr const & e, bool implicit) { expr const & fn = get_app_fn(e); if (!is_constant(fn)) return false; name const & mk_name = const_name(fn); if (!inductive::is_intro_rule(env, mk_name)) return false; name const & S = mk_name.get_prefix(); if (!is_structure(env, S)) return false; /* If implicit arguments is true, and the structure has parameters, we should not pretty print using { ... }, because we will not be able to see the parameters. */ if (implicit && *inductive::get_num_params(env, S) > 0) return false; /* check whether it is a partially applied constructor application */ if (get_app_num_args(e) != get_arity(env.get(mk_name).get_type())) return false; return true; } auto pretty_fn::pp_structure_instance(expr const & e) -> result { lean_assert(is_structure_instance(m_env, e, m_implict)); buffer args; expr const & mk = get_app_args(e, args); name const & S = const_name(mk).get_prefix(); unsigned num_params = *inductive::get_num_params(m_env, S); if (pp_using_anonymous_constructor(m_env, S)) { format r; for (unsigned i = num_params; i < args.size(); i++) { if (i > num_params) r += line(); format fval_fmt = pp(args[i]).fmt(); if (i < args.size() - 1) fval_fmt += comma(); r += fval_fmt; } r = group(nest(1, format("⟨") + r + format("⟩"))); return result(r); } else { auto fields = get_structure_fields(m_env, S); lean_assert(args.size() == num_params + fields.size()); format r; if (m_structure_instances_qualifier) r += format(S) + space() + format("."); for (unsigned i = 0; i < fields.size(); i++) { if (i > 0 || m_structure_instances_qualifier) r += line(); name fname = fields[i]; unsigned field_size = fname.utf8_size(); format fval_fmt = pp(args[i + num_params]).fmt(); if (i < fields.size() - 1) fval_fmt += comma(); r += format(fname) + space() + *g_assign_fmt + space() + nest(field_size + 4, fval_fmt); } r = group(nest(1, format("{") + r + format("}"))); return result(r); } } bool pretty_fn::is_field_notation_candidate(expr const & e) { expr const & f = get_app_fn(e); if (!is_constant(f)) return false; projection_info const * info = get_projection_info(m_env, const_name(f)); if (!info) return false; /* it is not a projection */ if (get_app_num_args(e) != info->m_nparams + 1) return false; /* If implicit arguments is true, and the structure has parameters, we should not pretty print using field notation because we will not be able to see the parameters. */ if (m_implict && info->m_nparams) return false; /* The @ explicitness annotation cannot be combined with field notation, so fail on implicit args */ if (m_implict && has_implicit_args(e)) return false; name const & S = const_name(f).get_prefix(); /* We should not use field notation with type classes since the structure is implicit. */ if (is_class(m_env, S)) return false; return true; } auto pretty_fn::pp_field_notation(expr const & e) -> result { lean_assert(is_field_notation_candidate(e)); buffer args; expr const & f = get_app_args(e, args); bool ignore_hide = true; format s_fmt = pp_child(args.back(), max_bp(), ignore_hide).fmt(); return result(max_bp()-1, s_fmt + format(".") + format(const_name(f).get_string())); } auto pretty_fn::pp_app(expr const & e) -> result { if (auto r = pp_local_ref(e)) return *r; if (is_default_arg_app(e)) return pp_child(app_fn(e), max_bp()); expr const & fn = app_fn(e); if (m_structure_instances && is_structure_instance(m_env, e, m_implict)) return pp_structure_instance(e); if (m_structure_projections && is_field_notation_candidate(e)) return pp_field_notation(e); // If the application contains a metavariable, then we want to // show the function, otherwise it would be hard to understand the // context where the metavariable occurs. This is hack to implement // formatter.hide_full_terms bool ignore_hide = true; result res_fn = pp_child(fn, max_bp()-1, ignore_hide); format fn_fmt = res_fn.fmt(); if (m_implict && (!is_app(fn) || (is_local_ref(m_env, fn))) && has_implicit_args(fn)) fn_fmt = compose(*g_explicit_fmt, fn_fmt); result res_arg = pp_child(app_arg(e), max_bp()); return result(max_bp()-1, group(compose(fn_fmt, nest(m_indent, compose(line(), res_arg.fmt()))))); } format pretty_fn::pp_binder(expr const & local) { format r; auto bi = local_info(local); if (bi != binder_info()) r += format(open_binder_string(bi, m_unicode)); r += escape(mlocal_pp_name(local)); if (m_binder_types) { r += space(); r += compose(colon(), nest(m_indent, compose(line(), pp_child(mlocal_type(local), 0).fmt()))); } if (bi != binder_info()) r += format(close_binder_string(bi, m_unicode)); return r; } format pretty_fn::pp_binder_block(buffer const & names, expr const & type, binder_info const & bi) { format r; if (m_binder_types || bi != binder_info()) r += format(open_binder_string(bi, m_unicode)); for (name const & n : names) { r += escape(n); } if (m_binder_types) { r += space(); r += compose(colon(), nest(m_indent, compose(line(), pp_child(type, 0).fmt()))); } if (m_binder_types || bi != binder_info()) r += format(close_binder_string(bi, m_unicode)); return group(r); } format pretty_fn::pp_binders(buffer const & locals) { unsigned num = locals.size(); buffer names; expr local = locals[0]; expr type = mlocal_type(local); binder_info bi = local_info(local); names.push_back(mlocal_pp_name(local)); format r; for (unsigned i = 1; i < num; i++) { expr local = locals[i]; if (!bi.is_inst_implicit() && mlocal_type(local) == type && local_info(local) == bi) { names.push_back(mlocal_pp_name(local)); } else { r += group(compose(line(), pp_binder_block(names, type, bi))); names.clear(); type = mlocal_type(local); bi = local_info(local); names.push_back(mlocal_pp_name(local)); } } r += group(compose(line(), pp_binder_block(names, type, bi))); return r; } auto pretty_fn::pp_lambda(expr const & e) -> result { expr b = e; buffer locals; while (is_lambda(b)) { auto p = binding_body_fresh(b, true); locals.push_back(p.second); b = p.first; } format r = m_unicode ? *g_lambda_n_fmt : *g_lambda_fmt; r += pp_binders(locals); r += group(compose(comma(), nest(m_indent, compose(line(), pp_child(b, 0).fmt())))); return result(0, r); } /** \brief Similar to #is_arrow, but only returns true if binder_info is the default one. That is, we don't want to lose binder info when pretty printing. */ static bool is_default_arrow(expr const & e) { return is_arrow(e) && binding_info(e) == binder_info(); } auto pretty_fn::pp_pi(expr const & e) -> result { if (is_default_arrow(e)) { result lhs = pp_child(binding_domain(e), get_arrow_prec()); expr b = lift_free_vars(binding_body(e), 1); result rhs = is_pi(b) ? pp(b) : pp_child(b, get_arrow_prec()-1); format r = group(lhs.fmt() + space() + (m_unicode ? *g_arrow_n_fmt : *g_arrow_fmt) + line() + rhs.fmt()); return result(get_arrow_prec(), get_arrow_prec()-1, r); } else { expr b = e; buffer locals; while (is_pi(b) && !is_default_arrow(b)) { auto p = binding_body_fresh(b, true); locals.push_back(p.second); b = p.first; } format r; if (is_prop(b)) r = m_unicode ? *g_forall_n_fmt : *g_forall_fmt; else r = m_unicode ? *g_pi_n_fmt : *g_pi_fmt; r += pp_binders(locals); r += group(compose(comma(), nest(m_indent, compose(line(), pp_child(b, 0).fmt())))); return result(0, r); } } static bool is_have(expr const & e) { return is_app(e) && is_have_annotation(app_fn(e)) && is_binding(get_annotation_arg(app_fn(e))); } static bool is_show(expr const & e) { return is_show_annotation(e) && is_app(get_annotation_arg(e)) && is_lambda(app_fn(get_annotation_arg(e))); } auto pretty_fn::pp_have(expr const & e) -> result { expr proof = app_arg(e); expr binding = get_annotation_arg(app_fn(e)); auto p = binding_body_fresh(binding, true); expr local = p.second; expr body = p.first; name const & n = mlocal_pp_name(local); format type_fmt = pp_child(mlocal_type(local), 0).fmt(); format proof_fmt = pp_child(proof, 0).fmt(); format body_fmt = pp_child(body, 0).fmt(); format head_fmt = *g_have_fmt; format r = head_fmt + space() + escape(n) + space(); r += colon() + nest(m_indent, line() + type_fmt + comma() + space() + *g_from_fmt); r = group(r); r += nest(m_indent, line() + proof_fmt + comma()); r = group(r); r += line() + body_fmt; return result(0, r); } auto pretty_fn::pp_show(expr const & e) -> result { lean_assert(is_show(e)); expr s = get_annotation_arg(e); expr proof = app_arg(s); expr type = binding_domain(app_fn(s)); format type_fmt = pp_child(type, 0).fmt(); format proof_fmt = pp_child(proof, 0).fmt(); format r = *g_show_fmt + space() + nest(5, type_fmt) + comma() + space() + *g_from_fmt; r = group(r); r += nest(m_indent, compose(line(), proof_fmt)); return result(0, group(r)); } auto pretty_fn::pp_explicit(expr const & e) -> result { result res_arg = pp_child(get_explicit_arg(e), max_bp()); return result(max_bp(), compose(*g_explicit_fmt, res_arg.fmt())); } auto pretty_fn::pp_delayed_abstraction(expr const & e) -> result { if (m_use_holes) { return pp_hole(); } else if (m_delayed_abstraction) { format r; r += format("["); buffer ns; buffer es; get_delayed_abstraction_info(e, ns, es); for (unsigned i = 0; i < ns.size(); i++) { format r2; if (i) r2 += format(",") + line(); r2 += pp(es[i]).fmt(); r += group(r2); } r += format("]"); return result(pp(get_delayed_abstraction_expr(e)).fmt() + nest(m_indent, r)); } else { return pp(get_delayed_abstraction_expr(e)); } } auto pretty_fn::pp_equation(expr const & e) -> format { lean_assert(is_equation(e)); format r = format("|"); buffer args; get_app_args(equation_lhs(e), args); for (expr const & arg : args) { r += space() + pp_child(arg, max_bp()).fmt(); } r += space() + *g_assign_fmt + space() + pp_child(equation_rhs(e), 0).fmt(); return r; } auto pretty_fn::pp_equations(expr const & e) -> optional { buffer eqns; unsigned num_fns = equations_num_fns(e); to_equations(e, eqns); buffer fns; expr eqn = eqns[0]; for (unsigned i = 0; i < num_fns; i++) { if (!is_lambda(eqn)) return optional(); if (!closed(binding_domain(eqn))) return optional(); auto p = binding_body_fresh(eqn, true); fns.push_back(p.second); eqn = p.first; } format r; if (num_fns > 1) { r = format("mutual_def") + space(); for (unsigned i = 0; i < num_fns; i++) { if (i > 0) r += format(", "); r += pp(fns[i]).fmt(); } r += line(); } else { r = format("def") + space() + pp(fns[0]).fmt() + space() + colon() + space() + pp(mlocal_type(fns[0])).fmt(); } unsigned eqnidx = 0; for (unsigned fidx = 0; fidx < num_fns; fidx++) { if (num_fns > 1) { if (fidx > 0) r += line(); r += format("with") + space() + pp(fns[fidx]).fmt() + space() + colon() + space() + pp(mlocal_type(fns[fidx])).fmt(); } if (eqnidx >= eqns.size()) return optional(); expr eqn = eqns[eqnidx]; while (is_lambda(eqn)) { eqn = binding_body_fresh(eqn, true).first; } eqnidx++; if (is_equation(eqn)) { r += line() + pp_equation(eqn); while (eqnidx < eqns.size()) { expr eqn = eqns[eqnidx]; while (is_lambda(eqn)) { eqn = binding_body_fresh(eqn, true).first; } if (is_equation(eqn) && get_app_fn(equation_lhs(eqn)) == fns[fidx]) { r += line() + pp_equation(eqn); eqnidx++; } else { break; } } } else { /* noequation */ r += line() + format("[none]"); } } if (eqns.size() != eqnidx) return optional(); return optional(r); } auto pretty_fn::pp_macro_default(expr const & e) -> result { // TODO(Leo): have macro annotations // fix macro<->pp interface if (is_prenum(e)) { return format(prenum_value(e).to_string()); } format r = compose(format("["), format(macro_def(e).get_name())); for (unsigned i = 0; i < macro_num_args(e); i++) r += nest(m_indent, compose(line(), pp_child(macro_arg(e, i), max_bp()).fmt())); r += format("]"); return result(group(r)); } auto pretty_fn::pp_macro(expr const & e) -> result { if (is_explicit(e)) { return pp_explicit(e); } else if (is_expr_quote(e)) { return result(format("`(") + nest(4, pp(get_expr_quote_value(e)).fmt()) + format(")")); } else if (is_pexpr_quote(e)) { return result(format("``(") + nest(2, pp(get_pexpr_quote_value(e)).fmt()) + format(")")); } else if (is_delayed_abstraction(e)) { return pp_delayed_abstraction(e); } else if (is_inaccessible(e)) { format r = format(".") + pp_child(get_annotation_arg(e), max_bp()).fmt(); return result(r); } else if (is_as_pattern(e)) { auto lhs_fmt = pp_child(get_as_pattern_lhs(e), max_bp()).fmt(); auto rhs_fmt = pp_child(get_as_pattern_rhs(e), max_bp()).fmt(); return result(lhs_fmt + format("@") + rhs_fmt); /* } else if (is_pattern_hint(e)) { return result(group(nest(2, format("(:") + pp(get_pattern_hint_arg(e)).fmt() + format(":)")))); */ } else if (is_marked_as_comp_irrelevant(e)) { if (m_hide_comp_irrel) return m_unicode ? format("◾") : format("irrel"); else return pp(get_annotation_arg(e)); } else if (!m_strings && to_string(e)) { expr n = *macro_def(e).expand(e, m_ctx); return pp(n); } else if (is_equations(e)) { if (auto r = pp_equations(e)) return *r; else return pp_macro_default(e); } else if (is_annotation(e)) { if (m_annotations) return format("[") + format(get_annotation_kind(e)) + space() + pp(get_annotation_arg(e)).fmt() + format("]"); else return pp(get_annotation_arg(e)); } else if (is_rec_fn_macro(e)) { return format("[") + format(get_rec_fn_name(e)) + format("]"); } else if (is_synthetic_sorry(e)) { if (m_use_holes) return pp_hole(); else return m_unicode ? format("⁇") : format("??"); } else if (is_sorry(e)) { if (m_use_holes) return pp_hole(); else return format("sorry"); } else { return pp_macro_default(e); } } auto pretty_fn::pp_let(expr e) -> result { buffer> decls; while (true) { expr t = let_type(e); expr v = let_value(e); expr b = let_body(e); auto p = let_body_fresh(e, true); decls.emplace_back(p.second, t, v); e = p.first; if (!is_let(e)) break; } lean_assert(!decls.empty()); format r = *g_let_fmt; unsigned sz = decls.size(); for (unsigned i = 0; i < sz; i++) { expr l, t, v; std::tie(l, t, v) = decls[i]; name const & n = mlocal_pp_name(l); format beg = i == 0 ? space() : line(); format sep = i < sz - 1 ? comma() : format(); format entry = format(n); format v_fmt = pp_child(v, 0).fmt(); if (is_neutral_expr(t)) { entry += space() + *g_assign_fmt + nest(m_indent, line() + v_fmt + sep); } else { format t_fmt = pp_child(t, 0).fmt(); entry += space() + colon() + space() + t_fmt + space() + *g_assign_fmt + nest(m_indent, line() + v_fmt + sep); } r += nest(3 + 1, beg + group(entry)); } format b = pp_child(e, 0).fmt(); r += line() + *g_in_fmt + space() + nest(2 + 1, b); return result(0, r); } auto pretty_fn::pp_num(mpz const & n, unsigned bp) -> result { if (n.is_neg()) { auto prec = get_expr_precedence(m_token_table, "-"); if (!prec || bp > *prec) { return result(paren(format(n.to_string()))); } } return result(format(n.to_string())); } // Return the number of parameters in a notation declaration. static unsigned get_num_parameters(notation_entry const & entry) { if (entry.is_numeral()) return 0; unsigned r = 0; if (!entry.is_nud()) r++; for (auto const & t : entry.get_transitions()) { switch (t.get_action().kind()) { case notation::action_kind::Skip: case notation::action_kind::Binder: case notation::action_kind::Binders: break; case notation::action_kind::Expr: case notation::action_kind::Exprs: case notation::action_kind::ScopedExpr: case notation::action_kind::Ext: r++; } } return r; } bool pretty_fn::match(level const & p, level const & l) { if (p == l) return true; if (m_universes) return false; if (is_placeholder(p)) return true; if (is_succ(p) && is_succ(l)) return match(succ_of(p), succ_of(l)); return false; } bool pretty_fn::match(expr const & p, expr const & e, buffer> & args) { if (is_explicit(p)) { return match(get_explicit_arg(p), e, args); } else if (is_as_atomic(p)) { return match(get_app_fn(get_as_atomic_arg(p)), e, args); } else if (is_var(p)) { unsigned vidx = var_idx(p); if (vidx >= args.size()) return false; unsigned i = args.size() - vidx - 1; if (args[i]) return *args[i] == e; args[i] = e; return true; } else if (is_placeholder(p)) { return true; } else if (is_constant(p) && is_constant(e)) { if (const_name(p) != const_name(e)) return false; levels p_ls = const_levels(p); levels e_ls = const_levels(p); while (!is_nil(p_ls)) { if (is_nil(e_ls)) return false; // e must have at least as many arguments as p if (!match(head(p_ls), head(e_ls))) return false; p_ls = tail(p_ls); e_ls = tail(e_ls); } return true; } else if (is_sort(p)) { if (!is_sort(e)) return false; return match(sort_level(p), sort_level(e)); } else if (is_app(e)) { buffer p_args, e_args; expr p_fn = get_app_args(p, p_args); expr e_fn = get_app_args(e, e_args); if (!match(p_fn, e_fn, args)) return false; if (is_explicit(p)) { if (p_args.size() != e_args.size()) return false; for (unsigned i = 0; i < p_args.size(); i++) { if (!match(p_args[i], e_args[i], args)) return false; } return true; } else { try { expr fn_type = m_ctx.infer(e_fn); unsigned j = 0; for (unsigned i = 0; i < e_args.size(); i++) { fn_type = m_ctx.relaxed_whnf(fn_type); if (!is_pi(fn_type)) return false; expr const & body = binding_body(fn_type); binder_info const & info = binding_info(fn_type); if (is_explicit(info)) { if (j >= p_args.size()) return false; if (!match(p_args[j], e_args[i], args)) return false; j++; } fn_type = instantiate(body, e_args[i]); } return j == p_args.size(); } catch (exception &) { return false; } } } else { return false; } } static unsigned get_some_precedence(token_table const & t, name const & tk) { if (tk.is_atomic() && tk.is_string()) { if (auto p = get_expr_precedence(t, tk.get_string())) return *p; } else { if (auto p = get_expr_precedence(t, tk.to_string().c_str())) return *p; } return 0; } auto pretty_fn::pp_notation_child(expr const & e, unsigned rbp, unsigned lbp) -> result { if (is_app(e)) { if (m_numerals) { if (auto n = to_num(e)) return pp_num(*n, lbp); } if (m_strings) { if (auto r = to_string(e)) return pp_string_literal(*r); if (auto r = to_char(m_ctx, e)) return pp_char_literal(*r); } expr const & f = app_fn(e); if (is_implicit(f)) { return pp_notation_child(f, rbp, lbp); } else if (!m_coercion && is_coercion(e)) { return pp_hide_coercion(e, rbp); } else if (!m_coercion && is_coercion_fn(e)) { return pp_hide_coercion_fn(e, rbp); } } result r = pp(e); /* see invariants of `pretty_fn::result`: Check that the surrounding notation would parse at least r * by the first invariant, and at most r (instead of the following token with binding power lbp) by the * second invariant. */ if (rbp < r.lbp() && r.rbp() >= lbp) { return r; } else { return result(paren(r.fmt())); } } static bool is_atomic_notation(notation_entry const & entry) { if (!entry.is_nud()) return false; list const & ts = entry.get_transitions(); if (!is_nil(tail(ts))) return false; return head(ts).get_action().kind() == notation::action_kind::Skip; } static format mk_tk_fmt(name const & tkn) { auto tk = tkn.to_string(); if (!tk.empty() && tk.back() == ' ') { tk.pop_back(); return format(tk) + line(); } else { return format(tk); } } auto pretty_fn::pp_notation(notation_entry const & entry, buffer> & args) -> optional { if (entry.is_numeral()) { return some(result(format(entry.get_num().to_string()))); } else if (is_atomic_notation(entry)) { format fmt = format(head(entry.get_transitions()).get_token()); return some(result(fmt)); } else { using notation::transition; buffer ts; buffer locals; // from scoped_expr to_buffer(entry.get_transitions(), ts); format fmt; unsigned i = ts.size(); // bp of the last action unsigned last_rbp = inf_bp()-1; // bp of the token immediately to the right of the action unsigned token_lbp = 0; bool last = true; while (i > 0) { --i; format curr; notation::action const & a = ts[i].get_action(); name const & tk = ts[i].get_token(); format tk_fmt = mk_tk_fmt(ts[i].get_pp_token()); switch (a.kind()) { case notation::action_kind::Skip: curr = tk_fmt; if (last) // invariant fulfilled: Skip action will never parse a trailing token last_rbp = inf_bp(); break; case notation::action_kind::Expr: if (args.empty() || !args.back()) { return optional(); } else { expr e = *args.back(); args.pop_back(); result e_r = pp_notation_child(e, a.rbp(), token_lbp); curr = tk_fmt + e_r.fmt(); if (last) /* last_rbp can be no more than `a.rbp()`, since this is the bp used during parsing. * However, `e_r.rbp()` may actually be smaller since it may be missing parentheses * compared to the original input. For example, when re-printing `x >>= (fun y, z) = ...`, * `e_r.fmt()` will be `fun y, z`. If we used the rbp of the `>>=` instead of * `e_r.rbp()` (0), we would get the wrong output `x >>= fun y, z = ...`. */ last_rbp = std::min(a.rbp(), e_r.rbp()); break; } case notation::action_kind::Exprs: if (args.empty() || !args.back()) { return optional(); } else { expr e = *args.back(); args.pop_back(); expr const & rec = a.get_rec(); optional const & ini = a.get_initial(); buffer rec_args; bool matched_once = false; while (true) { args.resize(args.size() + 2); if (!match(rec, e, args)) { args.pop_back(); args.pop_back(); break; } optional new_e = args.back(); args.pop_back(); optional rec_arg = args.back(); args.pop_back(); if (!new_e || !rec_arg) return optional(); rec_args.push_back(*rec_arg); e = *new_e; matched_once = true; } if (!matched_once && ini) return optional(); if (ini) { if (!match(*ini, e, args)) return optional(); } else { rec_args.push_back(e); } if (!a.is_fold_right()) std::reverse(rec_args.begin(), rec_args.end()); unsigned curr_lbp = token_lbp; if (auto t = a.get_terminator()) { curr = format(*t); curr_lbp = get_some_precedence(m_token_table, *t); } unsigned j = rec_args.size(); format sep_fmt = format(a.get_sep()); unsigned sep_lbp = get_some_precedence(m_token_table, a.get_sep()); while (j > 0) { --j; result arg_res = pp_notation_child(rec_args[j], a.rbp(), curr_lbp); if (j == 0) { curr = tk_fmt + arg_res.fmt() + curr; } else { curr = sep_fmt + arg_res.fmt() + curr; } curr_lbp = sep_lbp; } break; } case notation::action_kind::Binder: if (locals.size() != 1) return optional(); curr = tk_fmt + pp_binder(locals[0]); break; case notation::action_kind::Binders: curr = tk_fmt + pp_binders(locals); break; case notation::action_kind::ScopedExpr: if (args.empty() || !args.back()) { return optional(); } else { expr e = *args.back(); bool first_scoped = locals.empty(); unsigned i = 0; args.pop_back(); expr const & rec = a.get_rec(); while (true) { args.resize(args.size() + 1); if (!match(rec, e, args) || !args.back()) { args.pop_back(); break; } expr b = *args.back(); args.pop_back(); if (!((is_lambda(b) && a.use_lambda_abstraction()) || (is_pi(b) && !a.use_lambda_abstraction()))) { break; } auto p = binding_body_fresh(b, true); if (first_scoped) { locals.push_back(p.second); } else { if (i >= locals.size() || locals[i] != p.second) return optional(); } e = p.first; i++; } if (locals.empty()) return optional(); result e_r = pp_notation_child(e, a.rbp(), token_lbp); curr = tk_fmt + e_r.fmt(); if (last) // see Expr action last_rbp = std::min(a.rbp(), e_r.rbp()); break; } case notation::action_kind::Ext: return optional(); } token_lbp = get_some_precedence(m_token_table, tk); if (last) { fmt = curr; last = false; } else { fmt = curr + fmt; } } unsigned first_lbp = inf_bp(); if (!entry.is_nud()) { first_lbp = token_lbp; lean_assert(!last); if (args.size() != 1 || !args.back()) return optional(); expr e = *args.back(); args.pop_back(); format e_fmt = pp_notation_child(e, 0, token_lbp).fmt(); fmt = e_fmt + fmt; } return optional(result(first_lbp, last_rbp, group(nest(m_indent, fmt)))); } } auto pretty_fn::pp_notation(expr const & e) -> optional { if (!m_notation || is_var(e)) return optional(); for (notation_entry const & entry : get_notation_entries(m_env, head_index(e))) { if (entry.group() == notation_entry_group::Reserve) continue; if (!m_unicode && !entry.is_safe_ascii()) continue; // ignore this notation declaration since unicode support is not enabled unsigned num_params = get_num_parameters(entry); buffer> args; args.resize(num_params); if (match(entry.get_expr(), e, args)) { if (auto r = pp_notation(entry, args)) return r; } } return optional(); } static bool is_pp_atomic(expr const & e) { switch (e.kind()) { case expr_kind::App: case expr_kind::Lambda: case expr_kind::Pi: case expr_kind::Macro: return false; default: return true; } } static bool is_subtype(expr const & e) { return is_constant(get_app_fn(e), get_subtype_name()) && get_app_num_args(e) == 2 && is_lambda(app_arg(e)); } auto pretty_fn::pp_subtype(expr const & e) -> result { lean_assert(is_subtype(e)); expr pred = app_arg(e); lean_assert(is_lambda(pred)); auto p = binding_body_fresh(pred, true); expr body = p.first; expr local = p.second; format r = bracket("{", format(mlocal_pp_name(local)) + space() + format("//") + space() + pp_child(body, 0).fmt(), "}"); return result(r); } static bool is_emptyc(expr const & e) { return is_constant(get_app_fn(e), get_has_emptyc_emptyc_name()) && get_app_num_args(e) == 2; } static bool is_singleton(expr const & e) { return is_constant(get_app_fn(e), get_singleton_name()) && get_app_num_args(e) == 5; } static bool is_insert(expr const & e) { return is_constant(get_app_fn(e), get_has_insert_insert_name()) && get_app_num_args(e) == 5; } /* Return true iff 'e' encodes a nonempty finite collection, and stores its elements at elems. */ static bool is_explicit_collection(expr const & e, buffer & elems) { if (is_emptyc(e)) { return true; } else if (is_singleton(e)) { elems.push_back(app_arg(e)); return true; } else if (is_insert(e) && is_explicit_collection(app_arg(e), elems)) { elems.push_back(app_arg(app_fn(e))); return true; } else { return false; } } auto pretty_fn::pp_explicit_collection(buffer const & elems) -> result { if (elems.empty()) { return result(format(m_unicode ? "∅" : "{}")); } else { format r = pp_child(elems[0], 0).fmt(); for (unsigned i = 1; i < elems.size(); i++) { r += nest(m_indent, comma() + line() + pp_child(elems[i], 0).fmt()); } r = group(bracket("{", r, "}")); return result(r); } } bool is_set_of(expr const & e) { return is_constant(get_app_fn(e), get_set_of_name()) && get_app_num_args(e) == 2 && is_lambda(app_arg(e)); } auto pretty_fn::pp_set_of(expr const & e) -> result { lean_assert(is_set_of(e)); expr pred = app_arg(e); lean_assert(is_lambda(pred)); auto p = binding_body_fresh(pred, true); expr body = p.first; expr local = p.second; format r = bracket("{", pp_binder(local) + space() + format("|") + space() + pp_child(body, 0).fmt(), "}"); return result(r); } static bool is_sep(expr const & e) { return is_constant(get_app_fn(e), get_has_sep_sep_name()) && get_app_num_args(e) == 5 && is_lambda(app_arg(app_fn(e))); } auto pretty_fn::pp_sep(expr const & e) -> result { lean_assert(is_sep(e)); expr s = app_arg(e); expr pred = app_arg(app_fn(e)); lean_assert(is_lambda(pred)); auto p = binding_body_fresh(pred, true); expr body = p.first; expr local = p.second; format in = format(m_unicode ? "∈" : "in"); format r = bracket("{", format(mlocal_pp_name(local)) + space() + in + space() + pp_child(s, 0).fmt() + space() + format("|") + space() + pp_child(body, 0).fmt(), "}"); return result(r); } auto pretty_fn::pp_prod(expr const & e) -> result { format r = pp(app_arg(app_fn(e))).fmt(); auto it = app_arg(e); while (is_app_of(it, get_prod_mk_name(), 4)) { r += comma() + line(); r += pp(app_arg(app_fn(it))).fmt(); it = app_arg(it); } r += comma() + line(); r += pp(it).fmt(); return result(paren(group(r))); } auto pretty_fn::pp(expr const & e, bool ignore_hide) -> result { check_system("pretty printer"); if ((m_depth >= m_max_depth || m_num_steps > m_max_steps || (m_hide_full_terms && !ignore_hide && !has_expr_metavar(e))) && !is_pp_atomic(e)) { return result(m_unicode ? *g_ellipsis_n_fmt : *g_ellipsis_fmt); } flet let_d(m_depth, m_depth+1); m_num_steps++; if (m_numerals) { if (auto n = to_num(e)) return pp_num(*n, 0); } if (m_strings) { if (auto r = to_string(e)) return pp_string_literal(*r); if (auto r = to_char(m_ctx, e)) return pp_char_literal(*r); } try { if (!m_proofs && !is_constant(e) && !is_mlocal(e) && closed(e) && is_prop(m_ctx.infer(e))) { return result(format("_")); } } catch (exception) {} if (auto r = pp_notation(e)) return *r; if (m_notation) { if (is_subtype(e)) return pp_subtype(e); if (is_sep(e)) return pp_sep(e); if (is_set_of(e)) return pp_set_of(e); buffer elems; if (is_explicit_collection(e, elems)) return pp_explicit_collection(elems); if (is_app_of(e, get_prod_mk_name(), 4)) return pp_prod(e); } if (is_placeholder(e)) return result(*g_placeholder_fmt); if (is_show(e)) return pp_show(e); if (is_have(e)) return pp_have(e); if (is_typed_expr(e)) return pp(get_typed_expr_expr(e)); if (m_num_nat_coe) if (auto k = to_unsigned(e)) return format(*k); switch (e.kind()) { case expr_kind::Var: return pp_var(e); case expr_kind::Sort: return pp_sort(e); case expr_kind::Constant: return pp_const(e); case expr_kind::Meta: return pp_meta(e); case expr_kind::Local: return pp_local(e); case expr_kind::App: return pp_app(e); case expr_kind::Lambda: return pp_lambda(e); case expr_kind::Pi: return pp_pi(e); case expr_kind::Macro: return pp_macro(e); case expr_kind::Let: return pp_let(e); } lean_unreachable(); // LCOV_EXCL_LINE } pretty_fn::pretty_fn(environment const & env, options const & o, abstract_type_context & ctx): m_env(env), m_ctx(ctx), m_token_table(get_token_table(env)) { set_options_core(o); m_meta_prefix = "M"; m_next_meta_idx = 1; } // Custom beta reduction procedure for the pretty printer. // We don't want to reduce application in show annotations. class pp_beta_reduce_fn : public replace_visitor { virtual expr visit_meta(expr const & e) override { return e; } virtual expr visit_local(expr const & e) override { return e; } virtual expr visit_macro(expr const & e) override { if (is_show_annotation(e) && is_app(get_annotation_arg(e))) { expr const & n = get_annotation_arg(e); expr new_fn = visit(app_fn(n)); expr new_arg = visit(app_arg(n)); return mk_show_annotation(mk_app(new_fn, new_arg)); } else { return replace_visitor::visit_macro(e); } } virtual expr visit_app(expr const & e) override { expr new_e = replace_visitor::visit_app(e); if (is_head_beta(new_e)) return visit(head_beta_reduce(new_e)); else return new_e; } }; std::string sexpr_to_string(sexpr const & s) { if (is_string(s)) return to_string(s); std::stringstream ss; ss << s; return ss.str(); } // check whether a space must be inserted between the strings so that lexing them would // produce separate tokens std::pair pretty_fn::needs_space_sep(token_table const * last, std::string const & s1, std::string const & s2) const { if (s1.empty() || (is_id_rest(get_utf8_last_char(s1.data()), s1.data() + s1.size()) && is_id_rest(s2.data(), s2.data() + s2.size()))) return mk_pair(true, nullptr); // would be lexed as a single identifier without space if (last) { // complete deferred two-token lookahead for (char c : s2) { last = last->find(c); if (!last) break; if (last->value()) return mk_pair(true, nullptr); } if (last) { // would need an even larger lookahead, give up return mk_pair(true, nullptr); } } // check whether s1 + s2 has a longer prefix in the token table than s1 token_table const * t = &m_token_table; for (char c : s1) { t = t->find(c); if (!t) return mk_pair(false, nullptr); // s1 must be an identifier, and we know s2 does not start with is_id_rest } for (char c : s2) { t = t->find(c); if (!t) return mk_pair(false, nullptr); if (t->value()) return mk_pair(true, nullptr); } // the next identifier may expand s1 + s2 to a token, defer decision return mk_pair(false, t); } format pretty_fn::operator()(expr const & e) { auto purified = purify(m_beta ? pp_beta_reduce_fn()(e) : e); if (!m_options.contains(get_pp_proofs_name()) && !get_pp_all(m_options)) { try { m_proofs = !closed(purified) || is_prop(m_ctx.infer(purified)); } catch (exception) { m_proofs = true; } } m_depth = 0; m_num_steps = 0; result r = pp_child(purified, 0); // insert spaces so that lexing the result round-trips std::function sep; // NOLINT token_table const * last = nullptr; sep = [&](sexpr const & s1, sexpr const & s2) { bool b; std::tie(b, last) = needs_space_sep(last, sexpr_to_string(s1), sexpr_to_string(s2)); return b; }; return r.fmt().separate_tokens(sep); } formatter_factory mk_pretty_formatter_factory() { return [](environment const & env, options const & o, abstract_type_context & ctx) { // NOLINT auto fn_ptr = std::make_shared(env, o, ctx); return formatter(o, [=](expr const & e, options const & new_o) { fn_ptr->set_options(new_o); return (*fn_ptr)(e); }); }; } static options mk_options(bool detail) { options opts; if (detail) { opts = opts.update(name{"pp", "implicit"}, true); opts = opts.update(name{"pp", "notation"}, false); } return opts; } static void pp_core(environment const & env, expr const & e, bool detail) { type_checker tc(env); io_state ios(mk_pretty_formatter_factory(), mk_options(detail)); regular(env, ios, tc) << e << "\n"; } } // for debugging purposes void pp(lean::environment const & env, lean::expr const & e) { lean::pp_core(env, e, false); } void pp_detail(lean::environment const & env, lean::expr const & e) { lean::pp_core(env, e, true); }