lean2/src/frontends/lean/notation_cmd.cpp
2016-02-11 17:17:55 -08:00

896 lines
39 KiB
C++

/*
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 <utility>
#include <limits>
#include <string>
#include "util/sstream.h"
#include "util/utf8.h"
#include "kernel/abstract.h"
#include "kernel/replace_fn.h"
#include "library/scoped_ext.h"
#include "library/explicit.h"
#include "library/num.h"
#include "library/normalize.h"
#include "library/aliases.h"
#include "library/constants.h"
#include "library/typed_expr.h"
#include "frontends/lean/parser.h"
#include "frontends/lean/tokens.h"
#include "frontends/lean/util.h"
#include "frontends/lean/nested_declaration.h"
namespace lean {
static std::string parse_symbol(parser & p, char const * msg) {
name n;
if (p.curr_is_identifier() || p.curr_is_quoted_symbol()) {
n = p.get_name_val();
} else if (p.curr_is_keyword()) {
n = p.get_token_info().value();
} else {
throw parser_error(msg, p.pos());
}
p.next();
return n.to_string();
}
static unsigned parse_precedence_core(parser & p) {
auto pos = p.pos();
if (p.curr_is_numeral()) {
return p.parse_small_nat();
} else {
environment env = p.env();
env = open_prec_aliases(env);
parser::local_scope scope(p, env);
expr pre_val = p.parse_expr(get_max_prec());
pre_val = mk_typed_expr(mk_constant(get_num_name()), pre_val);
expr val = std::get<0>(p.elaborate(pre_val, list<expr>()));
val = normalize(p.env(), val);
if (optional<mpz> mpz_val = to_num_core(val)) {
if (!mpz_val->is_unsigned_int())
throw parser_error("invalid 'precedence', argument does not fit in a machine integer", pos);
return mpz_val->get_unsigned_int();
} else {
throw parser_error("invalid 'precedence', argument does not evaluate to a numeral", pos);
}
}
}
static optional<unsigned> parse_optional_precedence(parser & p) {
if (p.curr_is_token(get_colon_tk())) {
p.next();
return some(parse_precedence_core(p));
} else {
return optional<unsigned>();
}
}
static unsigned parse_precedence(parser & p) {
return parse_precedence_core(p);
}
LEAN_THREAD_VALUE(bool, g_allow_local, false);
static void check_notation_expr(expr const & e, pos_info const & pos) {
if (!g_allow_local && (has_local(e) || has_param_univ(e)))
throw parser_error("invalid notation declaration, contains reference to local variables", pos);
}
enum class mixfix_kind { infixl, infixr, postfix, prefix };
using notation::mk_expr_action;
using notation::mk_binder_action;
using notation::mk_binders_action;
using notation::mk_exprs_action;
using notation::mk_scoped_expr_action;
using notation::mk_skip_action;
using notation::transition;
using notation::action;
static char const * g_forbidden_tokens[] = {"!", "@", nullptr};
void check_not_forbidden(char const * tk) {
auto it = g_forbidden_tokens;
while (*it) {
if (strcmp(*it, tk) == 0)
throw exception(sstream() << "invalid token `" << tk << "`, it is reserved");
++it;
}
}
static optional<unsigned> get_precedence(environment const & env, char const * tk, bool is_expr) {
if (is_expr)
return get_expr_precedence(get_token_table(env), tk);
else
return get_tactic_precedence(get_token_table(env), tk);
}
static optional<unsigned> get_precedence(environment const & env, char const * tk, notation_entry_group grp) {
return get_precedence(env, tk, grp != notation_entry_group::Tactic);
}
static token_entry mk_token_entry(std::string const & tk, unsigned prec, bool is_expr) {
if (is_expr)
return mk_expr_token_entry(tk, prec);
else
return mk_tactic_token_entry(tk, prec);
}
static token_entry mk_token_entry(std::string const & tk, unsigned prec, notation_entry_group grp) {
return mk_token_entry(tk, prec, grp != notation_entry_group::Tactic);
}
static auto parse_mixfix_notation(parser & p, mixfix_kind k, bool overload, notation_entry_group grp, bool parse_only,
unsigned priority)
-> pair<notation_entry, optional<token_entry>> {
bool explicit_pp = p.curr_is_quoted_symbol();
std::string pp_tk = parse_symbol(p, "invalid notation declaration, quoted symbol or identifier expected");
std::string tk = utf8_trim(pp_tk);
char const * tks = tk.c_str();
check_not_forbidden(tks);
environment const & env = p.env();
optional<token_entry> new_token;
optional<unsigned> prec;
optional<parse_table> reserved_pt;
optional<transition> reserved_transition;
optional<action> reserved_action;
if (grp == notation_entry_group::Main) {
if (k == mixfix_kind::prefix) {
if (auto ls = get_reserved_nud_table(p.env()).find(tks)) {
// Remark: we are ignoring multiple actions in the reserved notation table
reserved_pt = head(ls).second;
reserved_transition = head(ls).first;
reserved_action = reserved_transition->get_action();
}
} else {
if (auto ls = get_reserved_led_table(p.env()).find(tks)) {
// Remark: we are ignoring multiple actions in the reserved notation table
reserved_pt = head(ls).second;
reserved_transition = head(ls).first;
reserved_action = reserved_transition->get_action();
}
}
}
if (p.curr_is_token(get_colon_tk())) {
// Remark: we do not throw an exception, if it is local notation.
// We allow local notation to override reserved one.
if (!g_allow_local && reserved_pt)
throw parser_error("invalid notation declaration, invalid ':' occurrence "
"(declaration matches reserved notation)", p.pos());
p.next();
prec = parse_precedence(p);
}
if (prec && k == mixfix_kind::infixr && *prec == 0)
throw parser_error("invalid infixr declaration, precedence must be greater than zero", p.pos());
if (!prec) {
if (reserved_action && k == mixfix_kind::prefix && reserved_action->kind() == notation::action_kind::Expr) {
lean_assert(grp == notation_entry_group::Main);
prec = reserved_action->rbp();
} else if (reserved_action && k == mixfix_kind::infixr && reserved_action->kind() == notation::action_kind::Expr) {
lean_assert(grp == notation_entry_group::Main);
prec = reserved_action->rbp();
} else {
prec = get_precedence(env, tk.c_str(), grp);
if (prec && k == mixfix_kind::infixr)
prec = *prec - 1;
}
} else {
auto old_prec = get_precedence(env, tk.c_str(), grp);
if (!old_prec || k != mixfix_kind::prefix)
new_token = mk_token_entry(tk.c_str(), *prec, grp);
if (k == mixfix_kind::infixr)
prec = *prec - 1;
}
if (!prec) {
lean_assert(!reserved_pt);
throw parser_error("invalid notation declaration, precedence was not provided, "
"and it is not set for the given symbol, "
"solution: use the 'precedence' command", p.pos());
}
unsigned _prec = 0;
if (prec) _prec = *prec; // this is a hack to fix an incorrect warning produced by clang++ on OSX
if (!g_allow_local && reserved_action) {
switch (k) {
case mixfix_kind::infixl:
if (reserved_action->kind() != notation::action_kind::Expr || reserved_action->rbp() != _prec)
throw parser_error("invalid infixl declaration, declaration conflicts with reserved notation", p.pos());
break;
case mixfix_kind::infixr:
if (reserved_action->kind() != notation::action_kind::Expr || reserved_action->rbp() != _prec)
throw parser_error("invalid infixr declaration, declaration conflicts with reserved notation", p.pos());
break;
case mixfix_kind::postfix:
if (reserved_action->kind() != notation::action_kind::Skip)
throw parser_error("invalid postfix declaration, declaration conflicts with reserved notation", p.pos());
break;
case mixfix_kind::prefix:
if (reserved_action->kind() != notation::action_kind::Expr || reserved_action->rbp() != _prec)
throw parser_error("invalid prefix declaration, declaration conflicts with reserved notation", p.pos());
break;
}
}
if (reserved_action && !explicit_pp)
pp_tk = reserved_transition->get_pp_token().to_string();
if (grp == notation_entry_group::Reserve) {
// reserve notation commands do not have a denotation
expr dummy = mk_Prop();
if (p.curr_is_token(get_assign_tk()))
throw parser_error("invalid reserve notation, found `:=`", p.pos());
switch (k) {
case mixfix_kind::infixl:
return mk_pair(notation_entry(false, to_list(transition(tks, mk_expr_action(*prec), pp_tk)),
dummy, overload, priority, grp, parse_only), new_token);
case mixfix_kind::infixr:
return mk_pair(notation_entry(false, to_list(transition(tks, mk_expr_action(*prec), pp_tk)),
dummy, overload, priority, grp, parse_only), new_token);
case mixfix_kind::postfix:
return mk_pair(notation_entry(false, to_list(transition(tks, mk_skip_action(), pp_tk)),
dummy, overload, priority, grp, parse_only), new_token);
case mixfix_kind::prefix:
return mk_pair(notation_entry(true, to_list(transition(tks, mk_expr_action(*prec), pp_tk)),
dummy, overload, priority, grp, parse_only), new_token);
}
} else {
p.check_token_next(get_assign_tk(), "invalid notation declaration, ':=' expected");
auto f_pos = p.pos();
expr f = p.parse_expr();
check_notation_expr(f, f_pos);
switch (k) {
case mixfix_kind::infixl:
#if defined(__GNUC__) && !defined(__CLANG__)
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
return mk_pair(notation_entry(false, to_list(transition(tks, mk_expr_action(*prec), pp_tk)),
mk_app(f, Var(1), Var(0)), overload, priority, grp, parse_only), new_token);
case mixfix_kind::infixr:
return mk_pair(notation_entry(false, to_list(transition(tks, mk_expr_action(*prec), pp_tk)),
mk_app(f, Var(1), Var(0)), overload, priority, grp, parse_only), new_token);
case mixfix_kind::postfix:
return mk_pair(notation_entry(false, to_list(transition(tks, mk_skip_action(), pp_tk)),
mk_app(f, Var(0)), overload, priority, grp, parse_only), new_token);
case mixfix_kind::prefix:
return mk_pair(notation_entry(true, to_list(transition(tks, mk_expr_action(*prec), pp_tk)),
mk_app(f, Var(0)), overload, priority, grp, parse_only), new_token);
}
}
lean_unreachable(); // LCOV_EXCL_LINE
}
static notation_entry parse_mixfix_notation(parser & p, mixfix_kind k, bool overload, notation_entry_group grp,
buffer<token_entry> & new_tokens, bool parse_only, unsigned priority) {
auto nt = parse_mixfix_notation(p, k, overload, grp, parse_only, priority);
if (nt.second)
new_tokens.push_back(*nt.second);
return nt.first;
}
static name parse_quoted_symbol_or_token(parser & p, buffer<token_entry> & new_tokens, bool & used_default, notation_entry_group grp) {
used_default = false;
if (p.curr_is_quoted_symbol()) {
environment const & env = p.env();
auto pp_tk = p.get_name_val();
auto tks = utf8_trim(pp_tk.to_string());
auto tkcs = tks.c_str();
check_not_forbidden(tkcs);
p.next();
if (p.curr_is_token(get_colon_tk())) {
p.next();
unsigned prec = parse_precedence(p);
new_tokens.push_back(mk_token_entry(tkcs, prec, grp));
} else if (!get_precedence(env, tkcs, grp)) {
new_tokens.push_back(mk_token_entry(tkcs, LEAN_DEFAULT_PRECEDENCE, grp));
used_default = true;
}
return pp_tk;
} else if (p.curr_is_keyword()) {
auto tk = p.get_token_info().token();
check_not_forbidden(tk.to_string().c_str());
p.next();
return tk;
} else {
throw parser_error("invalid notation declaration, symbol expected", p.pos());
}
}
static name parse_quoted_symbol_or_token(parser & p, buffer<token_entry> & new_tokens, notation_entry_group grp) {
bool dummy;
return parse_quoted_symbol_or_token(p, new_tokens, dummy, grp);
}
static expr parse_notation_expr(parser & p, buffer<expr> const & locals) {
auto pos = p.pos();
expr r = p.parse_expr();
r = abstract(r, locals.size(), locals.data());
check_notation_expr(r, pos);
return r;
}
static void parse_notation_local(parser & p, buffer<expr> & locals) {
if (p.curr_is_identifier()) {
name n = p.get_name_val();
p.next();
expr local_type = mk_Prop(); // type used in notation local declarations, it is irrelevant
expr l = mk_local(n, local_type); // remark: the type doesn't matter
p.add_local(l);
locals.push_back(l);
} else {
throw parser_error("invalid notation declaration, identifier expected", p.pos());
}
}
static unsigned get_precedence(environment const & env, buffer<token_entry> const & new_tokens, name const & token) {
std::string token_str = token.to_string();
for (auto const & e : new_tokens) {
if (e.m_token == token_str)
return e.m_prec;
}
auto prec = get_expr_precedence(get_token_table(env), token_str.c_str());
if (prec)
return *prec;
else
return 0;
}
static action parse_action(parser & p, name const & prev_token, unsigned default_prec,
buffer<expr> & locals, buffer<token_entry> & new_tokens, notation_entry_group grp) {
if (p.curr_is_token(get_colon_tk())) {
p.next();
if (p.curr_is_numeral() || p.curr_is_token_or_id(get_max_tk())) {
unsigned prec = parse_precedence(p);
return mk_expr_action(prec);
} else if (p.curr_is_token_or_id(get_prev_tk())) {
p.next();
return mk_expr_action(get_precedence(p.env(), new_tokens, prev_token));
} else if (p.curr_is_token_or_id(get_scoped_tk())) {
p.next();
return mk_scoped_expr_action(mk_var(0));
} else {
p.check_token_next(get_lparen_tk(), "invalid notation declaration, '(', numeral or 'scoped' expected");
if (p.curr_is_token_or_id(get_foldl_tk()) || p.curr_is_token_or_id(get_foldr_tk())) {
bool is_fold_right = p.curr_is_token_or_id(get_foldr_tk());
p.next();
auto prec = parse_optional_precedence(p);
name sep = parse_quoted_symbol_or_token(p, new_tokens, grp);
expr rec;
{
parser::local_scope scope(p);
p.check_token_next(get_lparen_tk(), "invalid fold notation argument, '(' expected");
parse_notation_local(p, locals);
parse_notation_local(p, locals);
p.check_token_next(get_comma_tk(), "invalid fold notation argument, ',' expected");
rec = parse_notation_expr(p, locals);
p.check_token_next(get_rparen_tk(), "invalid fold notation argument, ')' expected");
locals.pop_back();
locals.pop_back();
}
optional<expr> ini;
if (!p.curr_is_token(get_rparen_tk()) && !p.curr_is_quoted_symbol())
ini = parse_notation_expr(p, locals);
optional<name> terminator;
if (!p.curr_is_token(get_rparen_tk()))
terminator = parse_quoted_symbol_or_token(p, new_tokens, grp);
p.check_token_next(get_rparen_tk(), "invalid fold notation argument, ')' expected");
return mk_exprs_action(sep, rec, ini, terminator, is_fold_right, prec ? *prec : 0);
} else if (p.curr_is_token_or_id(get_scoped_tk())) {
p.next();
auto prec = parse_optional_precedence(p);
expr rec;
{
parser::local_scope scope(p);
parse_notation_local(p, locals);
p.check_token_next(get_comma_tk(), "invalid scoped notation argument, ',' expected");
rec = parse_notation_expr(p, locals);
locals.pop_back();
}
p.check_token_next(get_rparen_tk(), "invalid scoped notation argument, ')' expected");
return mk_scoped_expr_action(rec, prec ? *prec : 0);
} else {
throw parser_error("invalid notation declaration, 'foldl', 'foldr' or 'scoped' expected", p.pos());
}
}
} else {
return mk_expr_action(default_prec);
}
}
/**
\brief If the action for token \c tk in parse table \c pt is an Expr action,
then return its precedence. We use this value as the default precedence
when the user does not provide it. The idea is to minimize conflict with existing
notation.
*/
static unsigned get_default_prec(optional<parse_table> const & pt, name const & tk) {
if (!pt)
return LEAN_DEFAULT_PRECEDENCE;
if (auto ls = pt->find(tk)) {
for (auto at : ls) {
if (at.first.get_action().kind() == notation::action_kind::Expr)
return at.first.get_action().rbp();
}
}
return LEAN_DEFAULT_PRECEDENCE;
}
/** \brief Given a parsing table a \c pt and transition \c new_trans, if \c new_trans is a
transition in \c pt, then return the successor table */
static optional<parse_table> find_match(optional<parse_table> const & pt, transition const & new_trans) {
if (pt) {
if (auto ls = pt->find(new_trans.get_token())) {
for (auto at : ls) {
if (new_trans.get_action().is_equal(at.first.get_action()))
return optional<parse_table>(at.second);
}
}
}
return optional<parse_table>();
}
/** \brief Lift parse_table::find method to optional<parse_table> */
static list<pair<transition, parse_table>> find_next(optional<parse_table> const & pt, name const & tk) {
if (pt)
return pt->find(tk);
else
return list<pair<transition, parse_table>>();
}
static unsigned parse_binders_rbp(parser & p) {
if (p.curr_is_token(get_colon_tk())) {
p.next();
return parse_precedence(p);
} else {
return 0;
}
}
static transition parse_transition(parser & p, optional<parse_table> const & pt, name const & tk,
buffer<expr> & locals, buffer<token_entry> & new_tokens, notation_entry_group grp,
name const & pp_tk) {
if (p.curr_is_token_or_id(get_binder_tk())) {
p.next();
unsigned rbp = parse_binders_rbp(p);
return transition(tk, mk_binder_action(rbp), pp_tk);
} else if (p.curr_is_token_or_id(get_binders_tk())) {
p.next();
unsigned rbp = parse_binders_rbp(p);
return transition(tk, mk_binders_action(rbp), pp_tk);
} else if (p.curr_is_identifier()) {
unsigned default_prec = get_default_prec(pt, tk);
name n = p.get_name_val();
p.next();
action a = parse_action(p, tk, default_prec, locals, new_tokens, grp);
expr local_type = mk_Prop(); // type used in notation local declarations, it is irrelevant
expr l = mk_local(n, local_type);
p.add_local(l);
locals.push_back(l);
return transition(tk, a, pp_tk);
} else if (p.curr_is_quoted_symbol() || p.curr_is_keyword() ||
p.curr_is_token(get_assign_tk()) || p.curr_is_command() || p.curr_is_eof()) {
return transition(tk, mk_skip_action(), pp_tk);
} else {
throw parser_error("invalid notation declaration, quoted-symbol, identifier, "
"'binder', 'binders' expected", p.pos());
}
}
static notation_entry parse_notation_core(parser & p, bool overload, notation_entry_group grp, buffer<token_entry> & new_tokens, bool parse_only,
unsigned priority) {
buffer<expr> locals;
buffer<transition> ts;
parser::local_scope scope(p);
bool is_nud = true;
optional<parse_table> pt;
optional<parse_table> reserved_pt;
if (p.curr_is_numeral()) {
lean_assert(p.curr_is_numeral());
mpz num = p.get_num_val().get_numerator();
p.next();
p.check_token_next(get_assign_tk(), "invalid numeral notation, `:=` expected");
auto e_pos = p.pos();
expr e = p.parse_expr();
check_notation_expr(e, e_pos);
return notation_entry(num, e, overload, parse_only);
} else if (p.curr_is_identifier()) {
parse_notation_local(p, locals);
is_nud = false;
pt = get_led_table(p.env());
if (grp != notation_entry_group::Reserve)
reserved_pt = get_reserved_led_table(p.env());
} else {
pt = get_nud_table(p.env());
if (grp != notation_entry_group::Reserve)
reserved_pt = get_reserved_nud_table(p.env());
}
bool used_default = false;
while ((grp != notation_entry_group::Reserve && !p.curr_is_token(get_assign_tk())) ||
(grp == notation_entry_group::Reserve && !p.curr_is_command() && !p.curr_is_eof())) {
name pp_tk = parse_quoted_symbol_or_token(p, new_tokens, used_default, grp).to_string();
name tk = utf8_trim(pp_tk.to_string());
if (auto at = find_next(reserved_pt, tk)) {
// Remark: we are ignoring multiple actions in the reserved notation table
transition const & trans = head(at).first;
action const & a = trans.get_action();
reserved_pt = head(at).second;
if (!p.curr_is_quoted_symbol())
pp_tk = trans.get_pp_token();
switch (a.kind()) {
case notation::action_kind::Skip:
if (!p.curr_is_quoted_symbol() && !p.curr_is_keyword() && !p.curr_is_token(get_assign_tk())) {
if (g_allow_local && !p.curr_is_token_or_id(get_binders_tk())) {
ts.push_back(parse_transition(p, pt, tk, locals, new_tokens, grp, pp_tk));
break;
}
p.check_token_or_id_next(get_binders_tk(),
"invalid notation declaration, quoted-symbol, keyword or `:=` expected "
"(declaration prefix matches reserved notation)");
}
ts.push_back(transition(tk, a, pp_tk));
break;
case notation::action_kind::Binder:
if (g_allow_local && !p.curr_is_token_or_id(get_binder_tk())) {
ts.push_back(parse_transition(p, pt, tk, locals, new_tokens, grp, pp_tk));
break;
}
p.check_token_or_id_next(get_binder_tk(),
"invalid notation declaration, 'binder' expected "
"(declaration prefix matches reserved notation)");
ts.push_back(transition(tk, a, pp_tk));
break;
case notation::action_kind::Binders:
if (g_allow_local && !p.curr_is_token_or_id(get_binders_tk())) {
ts.push_back(parse_transition(p, pt, tk, locals, new_tokens, grp, pp_tk));
break;
}
p.check_token_or_id_next(get_binders_tk(),
"invalid notation declaration, 'binders' expected "
"(declaration prefix matches reserved notation)");
ts.push_back(transition(tk, a, pp_tk));
break;
case notation::action_kind::Expr: case notation::action_kind::Exprs: case notation::action_kind::ScopedExpr:
case notation::action_kind::Ext: {
if (g_allow_local && !p.curr_is_identifier()) {
ts.push_back(parse_transition(p, pt, tk, locals, new_tokens, grp, pp_tk));
break;
}
name n = p.check_id_next("invalid notation declaration, identifier expected "
"(declaration prefix matches reserved notation)");
if (p.curr_is_token(get_colon_tk())) {
if (g_allow_local) {
unsigned default_prec = get_default_prec(pt, tk);
action a = parse_action(p, tk, default_prec, locals, new_tokens, grp);
expr local_type = mk_Prop(); // type used in notation local declarations, it is irrelevant
expr l = mk_local(n, local_type);
p.add_local(l);
locals.push_back(l);
ts.push_back(transition(tk, a, pp_tk));
break;
} else {
throw parser_error("invalid notation declaration, invalid ':' occurrence "
"(declaration prefix matches reserved notation)", p.pos());
}
} else {
expr local_type = mk_Prop(); // type used in notation local declarations, it is irrelevant
expr l = mk_local(n, local_type);
p.add_local(l);
locals.push_back(l);
ts.push_back(transition(tk, a, pp_tk));
break;
}
}}
} else {
reserved_pt = optional<parse_table>();
ts.push_back(parse_transition(p, pt, tk, locals, new_tokens, grp, pp_tk));
}
pt = find_match(pt, ts.back());
}
// for atomic notation where binding power was not set, we set it to max
if (used_default && ts.size() == 1 && ts.back().get_action().kind() == notation::action_kind::Skip) {
lean_assert(!new_tokens.empty());
new_tokens.back().m_prec = get_max_prec();
}
expr n;
if (grp == notation_entry_group::Reserve) {
// reserve notation commands do not have a denotation
lean_assert(p.curr_is_command() || p.curr_is_eof());
expr dummy = mk_Prop(); // any expression without free variables will do
n = dummy;
} else {
lean_assert(p.curr_is_token(get_assign_tk()));
p.next();
if (ts.empty())
throw parser_error("invalid notation declaration, empty notation is not allowed", p.pos());
n = parse_notation_expr(p, locals);
}
return notation_entry(is_nud, to_list(ts.begin(), ts.end()), n, overload, priority, grp, parse_only);
}
bool curr_is_notation_decl(parser & p) {
return
p.curr_is_token(get_infix_tk()) || p.curr_is_token(get_infixl_tk()) || p.curr_is_token(get_infixr_tk()) ||
p.curr_is_token(get_postfix_tk()) || p.curr_is_token(get_prefix_tk()) || p.curr_is_token(get_notation_tk());
}
static notation_entry parse_notation(parser & p, bool overload, notation_entry_group grp, buffer<token_entry> & new_tokens,
bool allow_local) {
bool parse_only = false;
unsigned priority = LEAN_DEFAULT_NOTATION_PRIORITY;
flet<bool> set_allow_local(g_allow_local, allow_local);
if (p.curr_is_token(get_infix_tk()) || p.curr_is_token(get_infixl_tk())) {
p.next();
return parse_mixfix_notation(p, mixfix_kind::infixl, overload, grp, new_tokens, parse_only, priority);
} else if (p.curr_is_token(get_infixr_tk())) {
p.next();
return parse_mixfix_notation(p, mixfix_kind::infixr, overload, grp, new_tokens, parse_only, priority);
} else if (p.curr_is_token(get_postfix_tk())) {
p.next();
return parse_mixfix_notation(p, mixfix_kind::postfix, overload, grp, new_tokens, parse_only, priority);
} else if (p.curr_is_token(get_prefix_tk())) {
p.next();
return parse_mixfix_notation(p, mixfix_kind::prefix, overload, grp, new_tokens, parse_only, priority);
} else if (p.curr_is_token(get_notation_tk())) {
p.next();
return parse_notation_core(p, overload, grp, new_tokens, parse_only, priority);
} else {
throw parser_error("invalid notation, 'infix', 'infixl', 'infixr', 'prefix', "
"'postfix' or 'notation' expected", p.pos());
}
}
notation_entry parse_notation(parser & p, bool overload, buffer<token_entry> & new_tokens, bool allow_local) {
notation_entry_group grp = notation_entry_group::Main;
return parse_notation(p, overload, grp, new_tokens, allow_local);
}
static char g_reserved_chars[] = {',', 0};
static void check_token(char const * tk) {
if (!tk || !*tk)
throw exception("invalid null token");
if (tk[0] == '(')
throw exception(sstream() << "invalid token `" << tk << "`, it starts with '('");
unsigned sz = strlen(tk);
if (tk[sz-1] == ')')
throw exception(sstream() << "invalid token `" << tk << "`, it ends with ')'");
while (tk && *tk) {
unsigned sz = get_utf8_size(*tk);
if (sz == 0) {
throw exception(sstream() << "invalid token `" << tk << "`, contains invalid utf-8 character");
} else if (sz > 1) {
// multi-size unicode character
for (unsigned i = 0; i < sz; i++) {
if (!*tk)
throw exception(sstream() << "invalid token `" << tk << "`, contains invalid utf-8 character");
++tk;
}
} else {
auto it = g_reserved_chars;
while (*it) {
if (*tk == *it)
throw exception(sstream() << "invalid token `" << tk
<< "`, it contains reserved character `" << *it << "`");
++it;
}
++tk;
}
}
}
static environment add_user_token(environment const & env, token_entry const & e, bool persistent) {
check_token(e.m_token.c_str());
return add_token(env, e, persistent);
}
static environment add_user_token(environment const & env, char const * val, unsigned prec) {
check_token(val);
return add_expr_token(env, val, prec);
}
struct notation_modifiers {
bool m_parse_only;
unsigned m_priority;
notation_modifiers():m_parse_only(false), m_priority(LEAN_DEFAULT_NOTATION_PRIORITY) {}
void parse(parser & p) {
while (true) {
if (p.curr_is_token(get_parsing_only_tk())) {
p.next();
m_parse_only = true;
} else if (auto prio = parse_priority(p)) {
m_priority = *prio;
} else {
return;
}
}
}
};
static environment notation_cmd_core(parser & p, bool overload, notation_entry_group grp, bool persistent) {
notation_modifiers mods;
mods.parse(p);
flet<bool> set_allow_local(g_allow_local, !persistent);
environment env = p.env();
buffer<token_entry> new_tokens;
auto ne = parse_notation_core(p, overload, grp, new_tokens, mods.m_parse_only, mods.m_priority);
for (auto const & te : new_tokens)
env = add_user_token(env, te, persistent);
env = add_notation(env, ne, persistent);
return env;
}
static environment mixfix_cmd(parser & p, mixfix_kind k, bool overload, notation_entry_group grp, bool persistent) {
notation_modifiers mods;
mods.parse(p);
flet<bool> set_allow_local(g_allow_local, !persistent);
auto nt = parse_mixfix_notation(p, k, overload, grp, mods.m_parse_only, mods.m_priority);
environment env = p.env();
if (nt.second)
env = add_user_token(env, *nt.second, persistent);
env = add_notation(env, nt.first, persistent);
return env;
}
static environment notation_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = true;
notation_entry_group grp = notation_entry_group::Main;
bool persistent = true;
return notation_cmd_core(p, overload, grp, persistent);
}
static environment infixl_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = true;
notation_entry_group grp = notation_entry_group::Main;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::infixl, overload, grp, persistent);
}
static environment infixr_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = true;
notation_entry_group grp = notation_entry_group::Main;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::infixr, overload, grp, persistent);
}
static environment postfix_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = true;
notation_entry_group grp = notation_entry_group::Main;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::postfix, overload, grp, persistent);
}
static environment prefix_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = true;
notation_entry_group grp = notation_entry_group::Main;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::prefix, overload, grp, persistent);
}
static environment tactic_notation_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = false;
notation_entry_group grp = notation_entry_group::Tactic;
bool persistent = true;
return notation_cmd_core(p, overload, grp, persistent);
}
static environment tactic_infixl_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = false;
notation_entry_group grp = notation_entry_group::Tactic;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::infixl, overload, grp, persistent);
}
static environment tactic_infixr_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = false;
notation_entry_group grp = notation_entry_group::Tactic;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::infixr, overload, grp, persistent);
}
static environment tactic_postfix_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = false;
notation_entry_group grp = notation_entry_group::Tactic;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::postfix, overload, grp, persistent);
}
static environment tactic_prefix_cmd(parser & p) {
allow_nested_decls_scope scope(true);
bool overload = false;
notation_entry_group grp = notation_entry_group::Tactic;
bool persistent = true;
return mixfix_cmd(p, mixfix_kind::prefix, overload, grp, persistent);
}
// auxiliary procedure used by local_notation_cmd and reserve_cmd
static environment dispatch_notation_cmd(parser & p, bool overload, notation_entry_group grp, bool persistent) {
if (p.curr_is_token(get_notation_tk())) {
p.next();
return notation_cmd_core(p, overload, grp, persistent);
} else if (p.curr_is_token(get_infixl_tk())) {
p.next();
return mixfix_cmd(p, mixfix_kind::infixl, overload, grp, persistent);
} else if (p.curr_is_token(get_infix_tk())) {
p.next();
return mixfix_cmd(p, mixfix_kind::infixl, overload, grp, persistent);
} else if (p.curr_is_token(get_infixr_tk())) {
p.next();
return mixfix_cmd(p, mixfix_kind::infixr, overload, grp, persistent);
} else if (p.curr_is_token(get_prefix_tk())) {
p.next();
return mixfix_cmd(p, mixfix_kind::prefix, overload, grp, persistent);
} else if (p.curr_is_token(get_postfix_tk())) {
p.next();
return mixfix_cmd(p, mixfix_kind::postfix, overload, grp, persistent);
} else {
throw parser_error("invalid local/reserve notation, 'infix', 'infixl', 'infixr', 'prefix', "
"'postfix' or 'notation' expected", p.pos());
}
}
environment local_notation_cmd(parser & p) {
allow_nested_decls_scope scope(true);
parser::in_notation_ctx ctx(p);
bool overload = false; // REMARK: local notation override global one
notation_entry_group grp = notation_entry_group::Main;
bool persistent = false;
return dispatch_notation_cmd(p, overload, grp, persistent);
}
static environment reserve_cmd(parser & p) {
parser::in_notation_ctx ctx(p);
bool overload = false;
notation_entry_group grp = notation_entry_group::Reserve;
bool persistent = true;
return dispatch_notation_cmd(p, overload, grp, persistent);
}
static environment precedence_cmd(parser & p) {
std::string tk = parse_symbol(p, "invalid precedence declaration, quoted symbol or identifier expected");
p.check_token_next(get_colon_tk(), "invalid precedence declaration, ':' expected");
unsigned prec = parse_precedence(p);
return add_user_token(p.env(), tk.c_str(), prec);
}
void register_notation_cmds(cmd_table & r) {
add_cmd(r, cmd_info("precedence", "set token left binding power", precedence_cmd));
add_cmd(r, cmd_info("infixl", "declare a new infix (left) notation", infixl_cmd));
add_cmd(r, cmd_info("infix", "declare a new infix (left) notation", infixl_cmd));
add_cmd(r, cmd_info("infixr", "declare a new infix (right) notation", infixr_cmd));
add_cmd(r, cmd_info("postfix", "declare a new postfix notation", postfix_cmd));
add_cmd(r, cmd_info("prefix", "declare a new prefix notation", prefix_cmd));
add_cmd(r, cmd_info("notation", "declare a new notation", notation_cmd));
add_cmd(r, cmd_info("tactic_infixl", "declare a new tactic infix (left) notation", tactic_infixl_cmd));
add_cmd(r, cmd_info("tactic_infix", "declare a new tactic infix (left) notation", tactic_infixl_cmd));
add_cmd(r, cmd_info("tactic_infixr", "declare a new tactic infix (right) notation", tactic_infixr_cmd));
add_cmd(r, cmd_info("tactic_postfix", "declare a new tactic postfix notation", tactic_postfix_cmd));
add_cmd(r, cmd_info("tactic_prefix", "declare a new tactic prefix notation", tactic_prefix_cmd));
add_cmd(r, cmd_info("tactic_notation", "declare a new tacitc notation", tactic_notation_cmd));
add_cmd(r, cmd_info("reserve", "reserve notation", reserve_cmd));
}
bool is_notation_cmd(name const & n) {
return
n == get_infix_tk() || n == get_infixl_tk() || n == get_infixr_tk() || n == get_postfix_tk() ||
n == get_prefix_tk() || n == get_notation_tk() || n == get_precedence_tk() ||
n == get_tactic_infix_tk() || n == get_tactic_infixl_tk() || n == get_tactic_infixr_tk() || n == get_tactic_postfix_tk() ||
n == get_tactic_prefix_tk() || n == get_tactic_notation_tk();
}
}