feat(frontends/lean): improve tactic command parsing in interactive mode

Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
Leonardo de Moura 2013-12-05 16:28:08 -08:00
parent a1b5a8e50f
commit e3848d43a2
2 changed files with 267 additions and 159 deletions

View file

@ -96,6 +96,7 @@ static name g_exit_kwd("Exit");
static name g_apply("apply");
static name g_done("done");
static name g_back("back");
static list<name> g_tactic_cmds = { g_apply, g_done, g_back };
/** \brief Table/List with all builtin command keywords */
static list<name> g_command_keywords = {g_definition_kwd, g_variable_kwd, g_variables_kwd, g_theorem_kwd, g_axiom_kwd, g_universe_kwd, g_eval_kwd,
g_show_kwd, g_check_kwd, g_infix_kwd, g_infixl_kwd, g_infixr_kwd, g_notation_kwd, g_echo_kwd,
@ -159,6 +160,14 @@ class parser::imp {
virtual void rethrow() const { throw *this; }
};
struct tactic_cmd_error : public parser_error {
proof_state m_state;
tactic_cmd_error(char const * msg, pos_info const & p, proof_state const & s):parser_error(msg, p), m_state(s) {}
tactic_cmd_error(sstream const & msg, pos_info const & p, proof_state const & s):parser_error(msg, p), m_state(s) {}
virtual exception * clone() const { return new tactic_cmd_error(m_msg.c_str(), m_pos, m_state); }
virtual void rethrow() const { throw *this; }
};
template<typename F>
void using_script(F && f) {
m_script_state->apply([&](lua_State * L) {
@ -290,6 +299,152 @@ class parser::imp {
return r;
}
/**
@name Error handling
*/
/*@{*/
void display_error_pos(unsigned line, unsigned pos) {
regular(m_frontend) << "Error (line: " << line;
if (pos != static_cast<unsigned>(-1))
regular(m_frontend) << ", pos: " << pos;
regular(m_frontend) << ")";
}
void display_error_pos(pos_info const & p) { display_error_pos(p.first, p.second); }
void display_error_pos(expr const & e) {
if (e) {
auto it = m_expr_pos_info.find(e);
if (it == m_expr_pos_info.end())
return display_error_pos(m_last_cmd_pos);
else
return display_error_pos(it->second);
} else {
return display_error_pos(m_last_cmd_pos);
}
}
void display_error(char const * msg, unsigned line, unsigned pos) {
display_error_pos(line, pos);
regular(m_frontend) << " " << msg << endl;
}
void display_error(char const * msg) {
display_error(msg, m_scanner.get_line(), m_scanner.get_pos());
}
void display_error(kernel_exception const & ex) {
display_error_pos(m_elaborator.get_original(ex.get_main_expr()));
regular(m_frontend) << " " << ex << endl;
}
struct lean_pos_info_provider : public pos_info_provider {
imp const & m_ref;
lean_pos_info_provider(imp const & r):m_ref(r) {}
virtual std::pair<unsigned, unsigned> get_pos_info(expr const & e) const {
expr const & o = m_ref.m_elaborator.get_original(e);
auto it = m_ref.m_expr_pos_info.find(o);
if (it == m_ref.m_expr_pos_info.end())
throw exception("position is not available"); // information is not available
return it->second;
}
};
void display_error(elaborator_exception const & ex) {
formatter fmt = m_frontend.get_state().get_formatter();
options opts = m_frontend.get_state().get_options();
lean_pos_info_provider pos_provider(*this);
regular(m_frontend) << mk_pair(ex.get_justification().pp(fmt, opts, &pos_provider, true), opts) << endl;
}
void display_error(script_exception const & ex) {
switch (ex.get_source()) {
case script_exception::source::String:
display_error_pos(ex.get_line() + m_last_script_pos.first - 1, static_cast<unsigned>(-1));
regular(m_frontend) << " executing script," << ex.get_msg() << endl;
break;
case script_exception::source::File:
display_error_pos(m_last_script_pos);
regular(m_frontend) << " executing external script (" << ex.get_filename() << ":" << ex.get_line() << ")," << ex.get_msg() << endl;
break;
case script_exception::source::Unknown:
display_error_pos(m_last_script_pos);
regular(m_frontend) << " executing script, but could not decode position information, " << ex.what() << endl;
break;
}
}
void display_error(tactic_cmd_error const & ex) {
display_error(ex.what(), ex.m_pos.first, ex.m_pos.second);
if (m_interactive)
display_proof_state(ex.m_state);
}
/**
\brief Execute the given function \c f, and handle exceptions occurring
when executing \c f.
The parameter \c s is an error synchronization procedure.
*/
template<typename F, typename Sync>
void protected_call(F && f, Sync && sync) {
try {
f();
} catch (tactic_cmd_error & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex);
sync();
if (m_use_exceptions)
throw;
} catch (parser_error & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex.what(), ex.m_pos.first, ex.m_pos.second);
sync();
if (m_use_exceptions) {
throw parser_exception(ex.what(), ex.m_pos.first, ex.m_pos.second);
}
} catch (kernel_exception & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex);
sync();
if (m_use_exceptions)
throw;
} catch (elaborator_exception & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex);
sync();
if (m_use_exceptions)
throw;
} catch (script_exception & ex) {
m_found_errors = true;
reset_interrupt();
if (m_show_errors)
display_error(ex);
sync();
if (m_use_exceptions)
throw;
} catch (interrupted & ex) {
if (m_verbose)
regular(m_frontend) << "!!!Interrupted!!!" << endl;
reset_interrupt();
sync();
if (m_use_exceptions)
throw;
} catch (exception & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex.what());
sync();
if (m_use_exceptions)
throw;
}
}
/*@}*/
unsigned parse_unsigned(char const * msg) {
lean_assert(curr_is_nat());
mpz pval = curr_num().get_numerator();
@ -1073,13 +1228,19 @@ class parser::imp {
}
void display_proof_state(proof_state s) {
diagnostic(m_frontend) << "Proof state:\n" << s << endl;
regular(m_frontend) << "Proof state:\n" << s << endl;
}
void display_proof_state_if_interactive(proof_state s) {
if (m_interactive)
display_proof_state(s);
}
typedef std::vector<proof_state_seq> proof_state_seq_stack;
void tactic_backtrack(proof_state_seq_stack & stack, proof_state & s) {
if (!stack.empty())
diagnostic(m_frontend) << "Backtracking...\n";
auto p = pos();
next();
while (!stack.empty()) {
check_interrupted();
lazy_list<proof_state> seq = stack.back();
@ -1094,12 +1255,12 @@ class parser::imp {
return;
}
}
display_proof_state(s);
throw exception("failed to synthesize proof object using tactics");
throw tactic_cmd_error("no more states to backtrack", p, s);
}
void tactic_apply(proof_state_seq_stack & stack, proof_state & s) {
auto tac_pos = pos();
next();
tactic t;
if (curr() == scanner::token::ScriptBlock) {
parse_script_expr();
@ -1107,7 +1268,7 @@ class parser::imp {
if (is_tactic(L, -1))
t = to_tactic(L, -1);
else
throw parser_error(sstream() << "invalid script-block, it must return a tactic", tac_pos);
throw tactic_cmd_error(sstream() << "invalid script-block, it must return a tactic", tac_pos, s);
});
} else {
name tac_name = check_identifier_next("invalid apply command, identifier or 'script-block' expected");
@ -1116,7 +1277,7 @@ class parser::imp {
try {
t = to_tactic_ext(L, -1);
} catch (...) {
throw parser_error(sstream() << "unknown tactic '" << tac_name << "'", tac_pos);
throw tactic_cmd_error(sstream() << "unknown tactic '" << tac_name << "'", tac_pos, s);
}
lua_pop(L, 1);
});
@ -1131,11 +1292,13 @@ class parser::imp {
s = r->first;
stack.push_back(r->second);
} else {
tactic_backtrack(stack, s);
throw tactic_cmd_error("tactic failed", tac_pos, s);
}
}
expr tactic_done(proof_state const & s) {
auto p = pos();
next();
if (s.is_proof_final_state()) {
assignment a(s.get_menv());
proof_map m;
@ -1145,43 +1308,92 @@ class parser::imp {
}
return pr;
} else {
display_proof_state(s);
throw exception("failed to synthesize proof object using tactics");
throw tactic_cmd_error("failed to synthesize proof object using tactics", p, s);
}
}
bool curr_is_tactic_cmd() {
if (curr_is_identifier()) {
name const & id = curr_name();
for (auto c : g_tactic_cmds)
if (id == c)
return true;
}
return false;
}
void show_tactic_prompt() {
if (m_interactive) {
regular(m_frontend) << "## ";
regular(m_frontend).flush();
}
}
expr parse_tactic(proof_state s) {
proof_state ini = s;
proof_state_seq_stack stack;
while (true) {
auto p = pos();
if (!curr_is_identifier()) {
display_proof_state(s);
throw parser_error("invalid tactic, identifier expected", p);
}
name id = curr_name();
next();
if (id == g_apply) {
tactic_apply(stack, s);
} else if (id == g_back) {
tactic_backtrack(stack, s);
} else if (id == g_done) {
return tactic_done(s);
} else {
throw parser_error(sstream() << "invalid tactical proof, unknown command '" << id << "'", p);
}
while (curr_is_period())
next();
expr pr;
bool done = false;
while (!done) {
protected_call(
[&]() {
auto p = pos();
check_interrupted();
name id;
switch (curr()) {
case scanner::token::Period:
display_proof_state_if_interactive(s);
show_tactic_prompt();
next();
break;
case scanner::token::Eof:
done = true;
break;
case scanner::token::Id:
id = curr_name();
if (id == g_apply) {
tactic_apply(stack, s);
} else if (id == g_back) {
tactic_backtrack(stack, s);
} else if (id == g_done) {
done = true;
pr = tactic_done(s);
} else {
next();
throw tactic_cmd_error(sstream() << "invalid tactical proof, unknown command '" << id << "'", p, s);
}
break;
default:
next();
throw tactic_cmd_error("invalid tactic command, identifier expected", p, s);
}
},
[&]() {
// Keep consuming tokens until we find a Command or End-of-file or Tactic command
show_tactic_prompt();
while (curr() != scanner::token::CommandId && curr() != scanner::token::Eof &&
curr() != scanner::token::Period && !curr_is_tactic_cmd())
next();
if (curr_is_period())
next();
});
}
if (pr) {
return pr;
} else {
throw parser_error("invalid tactic command, unexpected end of file", pos());
}
}
expr parse_tactic(expr const & type, expr const & val, metavar_env & menv) {
check_period_next("invalid theorem, '.' expected before tactical proof");
if (is_metavar(val)) {
// simple case
if (!m_type_inferer.is_proposition(type))
throw exception("failed to synthesize metavar, its type is not a proposition");
proof_state s = to_proof_state(m_frontend, context(), type);
display_proof_state_if_interactive(s);
show_tactic_prompt();
check_period_next("invalid theorem, '.' expected before tactical proof");
return parse_tactic(s);
} else {
buffer<expr> mvars;
@ -1206,6 +1418,11 @@ class parser::imp {
}
context mvar_ctx(to_list(new_entries.begin(), new_entries.end()));
proof_state s = to_proof_state(m_frontend, mvar_ctx, mvar_type);
if (curr_is_period()) {
display_proof_state_if_interactive(s);
show_tactic_prompt();
next();
}
expr mvar_val = parse_tactic(s);
if (mvar_val)
menv.assign(mvar, mvar_val);
@ -1710,89 +1927,11 @@ class parser::imp {
}
/*@}*/
void display_error_pos(unsigned line, unsigned pos) {
regular(m_frontend) << "Error (line: " << line;
if (pos != static_cast<unsigned>(-1))
regular(m_frontend) << ", pos: " << pos;
regular(m_frontend) << ")";
}
void display_error_pos(pos_info const & p) { display_error_pos(p.first, p.second); }
void display_error_pos(expr const & e) {
if (e) {
auto it = m_expr_pos_info.find(e);
if (it == m_expr_pos_info.end())
return display_error_pos(m_last_cmd_pos);
else
return display_error_pos(it->second);
} else {
return display_error_pos(m_last_cmd_pos);
}
}
void display_error(char const * msg, unsigned line, unsigned pos) {
display_error_pos(line, pos);
regular(m_frontend) << " " << msg << endl;
sync();
}
void display_error(char const * msg) {
display_error(msg, m_scanner.get_line(), m_scanner.get_pos());
}
void display_error(kernel_exception const & ex) {
display_error_pos(m_elaborator.get_original(ex.get_main_expr()));
regular(m_frontend) << " " << ex << endl;
sync();
}
struct lean_pos_info_provider : public pos_info_provider {
imp const & m_ref;
lean_pos_info_provider(imp const & r):m_ref(r) {}
virtual std::pair<unsigned, unsigned> get_pos_info(expr const & e) const {
expr const & o = m_ref.m_elaborator.get_original(e);
auto it = m_ref.m_expr_pos_info.find(o);
if (it == m_ref.m_expr_pos_info.end())
throw exception("position is not available"); // information is not available
return it->second;
}
};
void display_error(elaborator_exception const & ex) {
formatter fmt = m_frontend.get_state().get_formatter();
options opts = m_frontend.get_state().get_options();
lean_pos_info_provider pos_provider(*this);
regular(m_frontend) << mk_pair(ex.get_justification().pp(fmt, opts, &pos_provider, true), opts) << endl;
}
void display_error(script_exception const & ex) {
switch (ex.get_source()) {
case script_exception::source::String:
display_error_pos(ex.get_line() + m_last_script_pos.first - 1, static_cast<unsigned>(-1));
regular(m_frontend) << " executing script," << ex.get_msg() << endl;
break;
case script_exception::source::File:
display_error_pos(m_last_script_pos);
regular(m_frontend) << " executing external script (" << ex.get_filename() << ":" << ex.get_line() << ")," << ex.get_msg() << endl;
break;
case script_exception::source::Unknown:
display_error_pos(m_last_script_pos);
regular(m_frontend) << " executing script, but could not decode position information, " << ex.what() << endl;
break;
}
}
void updt_options() {
m_verbose = get_parser_verbose(m_frontend.get_state().get_options());
m_show_errors = get_parser_show_errors(m_frontend.get_state().get_options());
}
/** \brief Keep consuming tokens until we find a Command or End-of-file. */
void sync() {
show_prompt();
while (curr() != scanner::token::CommandId && curr() != scanner::token::Eof)
next();
}
/**
\brief Parse a block of Lua code. If as_expr is true, then
it appends the string "return " in front of the script.
@ -1842,57 +1981,26 @@ public:
/** \brief Parse a sequence of commands. This method also perform error management. */
bool parse_commands() {
while (true) {
try {
switch (curr()) {
case scanner::token::CommandId: if (!parse_command()) return !m_found_errors; break;
case scanner::token::ScriptBlock: parse_script(); break;
case scanner::token::Period: show_prompt(); next(); break;
case scanner::token::Eof: return !m_found_errors;
default:
throw parser_error("Command expected", pos());
}
} catch (parser_error & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex.what(), ex.m_pos.first, ex.m_pos.second);
if (m_use_exceptions) {
throw parser_exception(ex.what(), ex.m_pos.first, ex.m_pos.second);
}
} catch (kernel_exception & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex);
if (m_use_exceptions)
throw;
} catch (elaborator_exception & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex);
if (m_use_exceptions)
throw;
} catch (script_exception & ex) {
m_found_errors = true;
reset_interrupt();
if (m_show_errors)
display_error(ex);
if (m_use_exceptions)
throw;
} catch (interrupted & ex) {
if (m_verbose)
regular(m_frontend) << "\n!!!Interrupted!!!" << endl;
reset_interrupt();
sync();
if (m_use_exceptions)
throw;
} catch (exception & ex) {
m_found_errors = true;
if (m_show_errors)
display_error(ex.what());
if (m_use_exceptions)
throw;
}
bool done = false;
while (!done) {
protected_call([&]() {
check_interrupted();
switch (curr()) {
case scanner::token::CommandId: if (!parse_command()) done = true; break;
case scanner::token::ScriptBlock: parse_script(); break;
case scanner::token::Period: show_prompt(); next(); break;
case scanner::token::Eof: done = true; break;
default:
throw parser_error("Command expected", pos());
}
}, [&]() {
// Keep consuming tokens until we find a Command or End-of-file
show_prompt();
while (curr() != scanner::token::CommandId && curr() != scanner::token::Eof && curr() != scanner::token::Period)
next();
});
}
return !m_found_errors;
}
/** \brief Parse an expression. */

View file

@ -135,7 +135,7 @@ A : Type, B : Type, a : ?M::0, b : ?M::1, C : Type ⊢ ?M::0[lift:0:3] ≺ C
Assumed: a
Assumed: b
Assumed: H
Error (line: 22, pos: 0) failed to synthesize metavar, its type is not a proposition
Error (line: 20, pos: 59) failed to synthesize metavar, its type is not a proposition
Failed to solve
⊢ b ≈ a
Substitution