Implement support for notation + implicit arguments. Cleanup pretty printer code for handling implicit arguments.

Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
Leonardo de Moura 2013-08-27 09:45:00 -07:00
parent 76c968a5b8
commit 206c7fa203
3 changed files with 302 additions and 193 deletions

View file

@ -97,14 +97,6 @@ struct frontend::imp {
return operator_info();
}
void diagnostic_msg(char const * msg) {
// TODO
}
void report_op_redefined(operator_info const & old_op, operator_info const & new_op) {
// TODO
}
/** \brief Remove all internal denotations that are associated with the given operator symbol (aka notation) */
void remove_bindings(operator_info const & op) {
for (expr const & d : op.get_exprs()) {
@ -124,6 +116,34 @@ struct frontend::imp {
insert(m_expr_to_operator, d, new_op);
}
/**
\brief Two operator (aka notation) denotations are compatible
iff one of the following holds:
1) Both do not have implicit arguments
2) Both have implicit arguments, and the implicit arguments
occur in the same positions.
*/
bool compatible_denotation(expr const & d1, expr const & d2) {
return get_implicit_arguments(d1) == get_implicit_arguments(d2);
}
/**
\brief Return true iff the existing denotations (aka
overloads) for an operator op are compatible with the new
denotation d.
The compatibility is only an issue if implicit arguments are
used. If one of the denotations has implicit arguments, then
all of them should have implicit arguments, and the implicit
arguments should occur in the same positions.
*/
bool compatible_denotations(operator_info const & op, expr const & d) {
return std::all_of(op.get_exprs().begin(), op.get_exprs().end(), [&](expr const & prev_d) { return compatible_denotation(prev_d, d); });
}
/**
\brief Add a new operator and save information as object.
@ -141,17 +161,23 @@ struct frontend::imp {
if (!old_op) {
register_new_op(new_op, d, led);
} else if (old_op == new_op) {
// overload
if (defined_here(old_op, led)) {
old_op.add_expr(d);
if (compatible_denotations(old_op, d)) {
// overload
if (defined_here(old_op, led)) {
old_op.add_expr(d);
} else {
// we must copy the operator because it was defined in
// a parent frontend.
new_op = old_op.copy();
register_new_op(new_op, d, led);
}
} else {
// we must copy the operator because it was defined in
// a parent frontend.
new_op = old_op.copy();
diagnostic(m_state) << "The denotation(s) for the existing notation:\n " << old_op << "\nhave been replaced with the new denotation:\n " << d << "\nbecause they conflict on how implicit arguments are used.\n";
remove_bindings(old_op);
register_new_op(new_op, d, led);
}
} else {
report_op_redefined(old_op, new_op);
diagnostic(m_state) << "Notation has been redefined, the existing notation:\n " << old_op << "\nhas been replaced with:\n " << new_op << "\nbecause they conflict with each other.\n";
remove_bindings(old_op);
register_new_op(new_op, d, led);
}
@ -214,6 +240,13 @@ struct frontend::imp {
}
}
std::vector<bool> const & get_implicit_arguments(expr const & n) {
if (is_constant(n))
return get_implicit_arguments(const_name(n));
else
return g_empty_vector;
}
name const & get_explicit_version(name const & n) {
auto it = m_implicit_table.find(n);
if (it != m_implicit_table.end()) {

View file

@ -400,43 +400,72 @@ class parser::imp {
return r;
}
/**
\brief Create an application for the given operator and
(explicit) arguments.
*/
expr mk_application(operator_info const & op, pos_info const & pos, unsigned num_args, expr const * args) {
buffer<expr> new_args;
expr f = save(mk_fun(op), pos);
new_args.push_back(f);
// I'm using the fact that all denotations are compatible.
// See lean_frontend.cpp for the definition of compatible denotations.
expr const & d = head(op.get_exprs());
if (is_constant(d) && m_frontend.has_implicit_arguments(const_name(d))) {
std::vector<bool> const & imp_args = m_frontend.get_implicit_arguments(const_name(d));
unsigned i = 0;
for (unsigned j = 0; j < imp_args.size(); j++) {
if (imp_args[j]) {
new_args.push_back(save(mk_metavar(), pos));
} else {
if (i >= num_args)
throw parser_error(sstream() << "unexpected number of arguments for denotation with implicit arguments, it expects " << num_args << " explicit argument(s)", pos);
new_args.push_back(args[i]);
i++;
}
}
} else {
new_args.append(num_args, args);
}
return save(mk_app(new_args.size(), new_args.data()), pos);
}
expr mk_application(operator_info const & op, pos_info const & pos, std::initializer_list<expr> const & l) {
return mk_application(op, pos, l.size(), l.begin());
}
expr mk_application(operator_info const & op, pos_info const & pos, expr const & arg) {
return mk_application(op, pos, 1, &arg);
}
expr mk_application(operator_info const & op, pos_info const & pos, buffer<expr> const & args) {
return mk_application(op, pos, args.size(), args.data());
}
/** \brief Parse a user defined prefix operator. */
expr parse_prefix(operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
expr arg = parse_expr(op.get_precedence());
return save(mk_app(f, arg), p);
return mk_application(op, p, parse_expr(op.get_precedence()));
}
/** \brief Parse a user defined postfix operator. */
expr parse_postfix(expr const & left, operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
return save(mk_app(f, left), p);
return mk_application(op, pos(), left);
}
/** \brief Parse a user defined infix operator. */
expr parse_infix(expr const & left, operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
expr right = parse_expr(op.get_precedence()+1);
return save(mk_app(f, left, right), p);
return mk_application(op, p, {left, parse_expr(op.get_precedence()+1)});
}
/** \brief Parse a user defined infix-left operator. */
expr parse_infixl(expr const & left, operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
expr right = parse_expr(op.get_precedence());
return save(mk_app(f, left, right), p);
return mk_application(op, p, {left, parse_expr(op.get_precedence())});
}
/** \brief Parse a user defined infix-right operator. */
expr parse_infixr(expr const & left, operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
expr right = parse_expr(op.get_precedence()-1);
return save(mk_app(f, left, right), p);
return mk_application(op, p, {left, parse_expr(op.get_precedence()-1)});
}
/**
@ -462,31 +491,25 @@ class parser::imp {
/** \brief Parse user defined mixfixl operator. It has the form: ID _ ... ID _ */
expr parse_mixfixl(operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
buffer<expr> args;
args.push_back(f);
args.push_back(parse_expr(op.get_precedence()));
parse_mixfix_args(op.get_op_name_parts(), op.get_precedence(), args);
return save(mk_app(args.size(), args.data()), p);
return mk_application(op, p, args);
}
/** \brief Parse user defined mixfixr operator. It has the form: _ ID ... _ ID */
expr parse_mixfixr(expr const & left, operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
buffer<expr> args;
args.push_back(f);
args.push_back(left);
parse_mixfix_args(op.get_op_name_parts(), op.get_precedence(), args);
return save(mk_app(args.size(), args.data()), p);
return mk_application(op, p, args);
}
/** \brief Parse user defined mixfixc operator. It has the form: ID _ ID ... _ ID */
expr parse_mixfixc(operator_info const & op) {
auto p = pos();
expr f = save(mk_fun(op), p);
buffer<expr> args;
args.push_back(f);
args.push_back(parse_expr(op.get_precedence()));
list<name> const & ops = op.get_op_name_parts();
auto it = ops.begin();
@ -495,22 +518,11 @@ class parser::imp {
check_op_part(*it);
++it;
if (it == ops.end())
return save(mk_app(args.size(), args.data()), p);
return mk_application(op, p, args);
args.push_back(parse_expr(op.get_precedence()));
}
}
/** \brief Return true iff all implicit arguments occur in the beginning. */
bool is_imp_arg_prefix(std::vector<unsigned> const & imp_args) {
for (unsigned i = 0; i < imp_args.size(); i++) {
// Remark: I'm using the fact that the implicit argument
// positions are sorted.
if (imp_args[i] != i)
return false;
}
return true;
}
/**
\brief Try to find an object (Definition or Postulate) named \c
id in the frontend/environment. If there isn't one, then tries
@ -1146,7 +1158,8 @@ class parser::imp {
unsigned prec = parse_precedence();
name op_id = parse_op_id();
check_colon_next("invalid operator definition, ':' expected");
expr d = parse_expr();
name name_id = check_identifier_next("invalid operator definition, identifier expected");
expr d = mk_constant(name_id);
switch (fx) {
case fixity::Prefix: m_frontend.add_prefix(op_id, prec, d); break;
case fixity::Postfix: m_frontend.add_postfix(op_id, prec, d); break;
@ -1174,7 +1187,8 @@ class parser::imp {
}
lean_assert(curr_is_colon());
next();
expr d = parse_expr();
name name_id = check_identifier_next("invalid operator definition, identifier expected");
expr d = mk_constant(name_id);
switch (fx) {
case fixity::Mixfixl: m_frontend.add_mixfixl(parts.size(), parts.data(), prec, d); break;
case fixity::Mixfixr: m_frontend.add_mixfixr(parts.size(), parts.data(), prec, d); break;

View file

@ -200,59 +200,6 @@ class pp_fn {
}
}
/**
\brief Return the operator associated with \c e.
Return the null operator if there is none.
We say \c e has an operator associated with it, if:
1) \c e is associated with an operator.
2) It is an application, and the function is associated with an
operator.
*/
operator_info get_operator(expr const & e) {
operator_info op = m_frontend.find_op_for(e);
if (op)
return op;
else if (is_app(e))
return m_frontend.find_op_for(arg(e, 0));
else
return operator_info();
}
/**
\brief Return the precedence of the given expression
*/
unsigned get_operator_precedence(expr const & e) {
if (is_constant(e)) {
operator_info op = get_operator(e);
return op ? op.get_precedence() : 0;
} else if (is_eq(e)) {
return g_eq_precedence;
} else if (is_arrow(e)) {
return g_arrow_precedence;
} else {
return 0;
}
}
/** \brief Return true if the application \c e has the number of arguments expected by the operator \c op. */
bool has_expected_num_args(expr const & e, operator_info const & op) {
switch (op.get_fixity()) {
case fixity::Infix: case fixity::Infixl: case fixity::Infixr:
return num_args(e) == 3;
case fixity::Prefix: case fixity::Postfix:
return num_args(e) == 2;
case fixity::Mixfixl: case fixity::Mixfixr:
return num_args(e) == length(op.get_op_name_parts()) + 1;
case fixity::Mixfixc:
return num_args(e) == length(op.get_op_name_parts());
}
lean_unreachable();
return false;
}
/**
\brief Pretty print given expression and put parenthesis around
it IF the pp of the expression is not a simple name.
@ -280,42 +227,6 @@ class pp_fn {
return pp_child_with_paren(e, depth);
}
/**
\brief Pretty print the child of an infix, prefix, postfix or
mixfix operator. It will add parethesis when needed.
*/
result pp_mixfix_child(operator_info const & op, expr const & e, unsigned depth) {
if (is_atomic(e)) {
return pp(e, depth + 1);
} else {
if (op.get_precedence() < get_operator_precedence(e))
return pp(e, depth + 1);
else
return pp_child_with_paren(e, depth);
}
}
/**
\brief Pretty print the child of an associative infix
operator. It will add parethesis when needed.
*/
result pp_infix_child(operator_info const & op, expr const & e, unsigned depth) {
if (is_atomic(e)) {
return pp(e, depth + 1);
} else {
if (op.get_precedence() < get_operator_precedence(e) || op == get_operator(e))
return pp(e, depth + 1);
else
return pp_child_with_paren(e, depth);
}
}
result mk_infix(operator_info const & op, result const & lhs, result const & rhs) {
unsigned r_weight = lhs.second + rhs.second + 1;
format r_format = group(format{lhs.first, space(), format(op.get_op_name()), line(), rhs.first});
return mk_result(r_format, r_weight);
}
bool is_forall_expr(expr const & e) {
return is_app(e) && arg(e, 0) == mk_forall_fn() && num_args(e) == 3;
}
@ -359,6 +270,8 @@ class pp_fn {
}
}
/** \brief Auxiliary function for pretty printing exists and
forall formulas */
result pp_quantifier(expr const & e, unsigned depth, bool is_forall) {
buffer<std::pair<name, expr>> nested;
expr b = collect_nested_quantifiers(e, is_forall, nested);
@ -416,38 +329,214 @@ class pp_fn {
return pp_quantifier(e, depth, false);
}
/**
\brief Return the operator associated with \c e.
Return the null operator if there is none.
We say \c e has an operator associated with it, if:
1) \c e is associated with an operator.
2) It is an application, and the function is associated with an
operator.
*/
operator_info get_operator(expr const & e) {
operator_info op = m_frontend.find_op_for(e);
if (op)
return op;
else if (is_app(e))
return m_frontend.find_op_for(arg(e, 0));
else
return operator_info();
}
/**
\brief Return the precedence of the given expression
*/
unsigned get_operator_precedence(expr const & e) {
if (is_constant(e)) {
operator_info op = get_operator(e);
return op ? op.get_precedence() : 0;
} else if (is_eq(e)) {
return g_eq_precedence;
} else if (is_arrow(e)) {
return g_arrow_precedence;
} else {
return 0;
}
}
/**
\brief Pretty print the child of an infix, prefix, postfix or
mixfix operator. It will add parethesis when needed.
*/
result pp_mixfix_child(operator_info const & op, expr const & e, unsigned depth) {
if (is_atomic(e)) {
return pp(e, depth + 1);
} else {
if (op.get_precedence() < get_operator_precedence(e))
return pp(e, depth + 1);
else
return pp_child_with_paren(e, depth);
}
}
/**
\brief Pretty print the child of an associative infix
operator. It will add parethesis when needed.
*/
result pp_infix_child(operator_info const & op, expr const & e, unsigned depth) {
if (is_atomic(e)) {
return pp(e, depth + 1);
} else {
if (op.get_precedence() < get_operator_precedence(e) || op == get_operator(e))
return pp(e, depth + 1);
else
return pp_child_with_paren(e, depth);
}
}
result mk_infix(operator_info const & op, result const & lhs, result const & rhs) {
unsigned r_weight = lhs.second + rhs.second + 1;
format r_format = group(format{lhs.first, space(), format(op.get_op_name()), line(), rhs.first});
return mk_result(r_format, r_weight);
}
/**
\brief Wrapper for accessing the explicit arguments of an
application and its function.
\remark If show_implicit is true, then we show the explicit
arguments even if the function has implicit arguments.
\remark We also show the implicit arguments, if the
application does not have the necessary number of
arguments.
\remark When we expose the implicit arguments, we use the
explicit version of the function. So, the user can
understand the pretty printed term.
*/
struct application {
expr const & m_app;
expr m_f;
std::vector<bool> const * m_implicit_args;
bool m_notation_enabled;
application(expr const & e, frontend const & fe, bool show_implicit):m_app(e) {
expr const & f = arg(e,0);
if (is_constant(f) && fe.has_implicit_arguments(const_name(f))) {
m_implicit_args = &(fe.get_implicit_arguments(const_name(f)));
if (show_implicit || num_args(e) - 1 < m_implicit_args->size()) {
// we are showing implicit arguments, thus we do
// not need the bit-mask for implicit arguments
m_implicit_args = nullptr;
// we use the explicit name of f, to make it clear
// that we are exposing implicit arguments
m_f = mk_constant(fe.get_explicit_version(const_name(f)));
m_notation_enabled = false;
} else {
m_f = f;
m_notation_enabled = true;
}
} else {
m_f = f;
m_implicit_args = nullptr;
m_notation_enabled = true;
}
}
unsigned get_num_args() const {
if (m_implicit_args) {
unsigned r = 0;
for (unsigned i = 0; i < num_args(m_app) - 1; i++) {
if (!(*m_implicit_args)[i])
r++;
}
return r;
} else {
return num_args(m_app) - 1;
}
}
expr const & get_arg(unsigned i) const {
lean_assert(i < get_num_args());
if (m_implicit_args) {
unsigned n = num_args(m_app);
for (unsigned j = 1; j < n; j++) {
if (!(*m_implicit_args)[j-1]) {
// explicit argument found
if (i == 0)
return arg(m_app, j);
--i;
}
}
lean_unreachable();
return arg(m_app, n - 1);
} else {
return arg(m_app, i + 1);
}
}
expr const & get_function() const {
return m_f;
}
bool notation_enabled() const {
return m_notation_enabled;
}
};
/** \brief Return true if the application \c app has the number of arguments expected by the operator \c op. */
bool has_expected_num_args(application const & app, operator_info const & op) {
switch (op.get_fixity()) {
case fixity::Infix: case fixity::Infixl: case fixity::Infixr:
return app.get_num_args() == 2;
case fixity::Prefix: case fixity::Postfix:
return app.get_num_args() == 1;
case fixity::Mixfixl: case fixity::Mixfixr:
return app.get_num_args() == length(op.get_op_name_parts()) + 1;
case fixity::Mixfixc:
return app.get_num_args() == length(op.get_op_name_parts());
}
lean_unreachable();
return false;
}
/**
\brief Pretty print an application.
*/
result pp_app(expr const & e, unsigned depth) {
application app(e, m_frontend, m_implict);
operator_info op;
if (m_notation && (op = get_operator(e)) && has_expected_num_args(e, op)) {
if (m_notation && app.notation_enabled() && (op = get_operator(e)) && has_expected_num_args(app, op)) {
result p_arg;
format r_format;
unsigned sz;
unsigned r_weight = 1;
switch (op.get_fixity()) {
case fixity::Infix:
return mk_infix(op, pp_mixfix_child(op, arg(e, 1), depth), pp_mixfix_child(op, arg(e, 2), depth));
return mk_infix(op, pp_mixfix_child(op, app.get_arg(0), depth), pp_mixfix_child(op, app.get_arg(1), depth));
case fixity::Infixr:
return mk_infix(op, pp_mixfix_child(op, arg(e, 1), depth), pp_infix_child(op, arg(e, 2), depth));
return mk_infix(op, pp_mixfix_child(op, app.get_arg(0), depth), pp_infix_child(op, app.get_arg(1), depth));
case fixity::Infixl:
return mk_infix(op, pp_infix_child(op, arg(e, 1), depth), pp_mixfix_child(op, arg(e, 2), depth));
return mk_infix(op, pp_infix_child(op, app.get_arg(0), depth), pp_mixfix_child(op, app.get_arg(1), depth));
case fixity::Prefix:
p_arg = pp_infix_child(op, arg(e, 1), depth);
p_arg = pp_infix_child(op, app.get_arg(0), depth);
sz = op.get_op_name().size();
return mk_result(group(format{format(op.get_op_name()), nest(sz+1, format{line(), p_arg.first})}),
p_arg.second + 1);
case fixity::Postfix:
p_arg = pp_mixfix_child(op, arg(e, 1), depth);
p_arg = pp_mixfix_child(op, app.get_arg(0), depth);
return mk_result(group(format{p_arg.first, space(), format(op.get_op_name())}),
p_arg.second + 1);
case fixity::Mixfixr: {
// _ ID ... _ ID
list<name> parts = op.get_op_name_parts();
auto it = parts.begin();
for (unsigned i = 1; i < num_args(e); i++) {
result p_arg = pp_mixfix_child(op, arg(e, i), depth);
unsigned num = app.get_num_args();
for (unsigned i = 0; i < num; i++) {
result p_arg = pp_mixfix_child(op, app.get_arg(i), depth);
r_format += format{p_arg.first, space(), format(*it), line()};
r_weight += p_arg.second;
++it;
@ -459,8 +548,9 @@ class pp_fn {
// ID _ ... _ ID
list<name> parts = op.get_op_name_parts();
auto it = parts.begin();
for (unsigned i = 1; i < num_args(e); i++) {
result p_arg = pp_mixfix_child(op, arg(e, i), depth);
unsigned num = app.get_num_args();
for (unsigned i = 0; i < num; i++) {
result p_arg = pp_mixfix_child(op, app.get_arg(i), depth);
unsigned sz = it->size();
if (i > 1) r_format += space();
r_format += format{format(*it), nest(sz+1, format{line(), p_arg.first})};
@ -481,47 +571,17 @@ class pp_fn {
return pp_exists(e, depth);
} else {
// standard function application
expr f = arg(e, 0);
std::vector<bool> const * implicit_args = nullptr;
format r_format;
unsigned r_weight;
bool simple = false;
unsigned indent = m_indent;
if (is_constant(f)) {
name const & n = const_name(f);
simple = const_name(f).size() <= m_indent + 4;
indent = simple ? const_name(f).size()+1 : m_indent;
if (m_frontend.has_implicit_arguments(n)) {
implicit_args = &(m_frontend.get_implicit_arguments(n));
lean_assert(implicit_args->size() != 0);
if (m_implict || num_args(e) - 1 < implicit_args->size()) {
// If implicit arguments should be displayed, or
// If we do not have enough arguments, then
// we use the explicit representation
implicit_args = nullptr;
// we should use the explicit version of the
// definition, since we are not hiding implicit arguments
r_format = format(m_frontend.get_explicit_version(n));
simple = false;
indent = m_indent;
} else {
r_format = format(n);
}
} else {
r_format = format(n);
}
r_weight = 1;
} else {
result p = pp_child(f, depth);
r_format = p.first;
r_weight = p.second;
}
for (unsigned i = 1; i < num_args(e); i++) {
if (!is_implicit(implicit_args, i-1)) {
result p_arg = pp_child(arg(e, i), depth);
r_format += format{i == 1 && simple ? space() : line(), p_arg.first};
r_weight += p_arg.second;
}
expr const & f = app.get_function();
result p = is_constant(f) ? mk_result(format(const_name(f)),1) : pp_child(f, depth);
bool simple = is_constant(f) && const_name(f).size() <= m_indent + 4;
unsigned indent = simple ? const_name(f).size()+1 : m_indent;
format r_format = p.first;
unsigned r_weight = p.second;
unsigned num = app.get_num_args();
for (unsigned i = 0; i < num; i++) {
result p_arg = pp_child(app.get_arg(i), depth);
r_format += format{i == 0 && simple ? space() : line(), p_arg.first};
r_weight += p_arg.second;
}
return mk_result(group(nest(indent, r_format)), r_weight);
}
@ -970,7 +1030,9 @@ class pp_formatter_cell : public formatter_cell {
format pp_notation_decl(object const & obj, options const & opts) {
notation_declaration const & n = *(static_cast<notation_declaration const *>(obj.cell()));
return format{::lean::pp(n.get_op()), space(), colon(), space(), pp(n.get_expr(), opts)};
expr const & d = n.get_expr();
format d_fmt = is_constant(d) ? format(const_name(d)) : pp(d, opts);
return format{::lean::pp(n.get_op()), space(), colon(), space(), d_fmt};
}
public: