feat(frontends/lean/server): add VISIT and CHECK commands

Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
Leonardo de Moura 2014-08-11 10:40:18 -07:00
parent 71583ba9c8
commit faf2795a7b
3 changed files with 199 additions and 98 deletions

View file

@ -13,13 +13,27 @@ to the standard input.
** Load file ** Load file
#+BEGIN_SRC #+BEGIN_SRC
FILE [file-name] LOAD [file-name]
#+END_SRC #+END_SRC
This command loads the Lean file named =[file-name]=. This command loads the Lean file named =[file-name]=.
Lean will create a "snapshot" (aka backtracking point) after each Lean will create a "snapshot" (aka backtracking point) after each
command. Lean uses the "snapshots" to process incremental updates efficiently. command. Lean uses the "snapshots" to process incremental updates efficiently.
** Visit file
#+BEGIN_SRC
VISIT [file-name]
#+END_SRC
Lean can keep information about multiple files. This command
sets =[file-name]= as the "current" file. The remaining commands
are all with respect to the current file. If =[file-name]= has not been
loaded yet, then this command will load it. Some of the remaining commands
apply "changes" to the current file. The =LOAD= command can be used to
discard all these changes, and enforce the content of the file stored
in file system.
** Replace line ** Replace line
#+BEGIN_SRC #+BEGIN_SRC
@ -27,7 +41,7 @@ REPLACE [line-number]
[new-line] [new-line]
#+END_SRC #+END_SRC
This command replaces the line =[line-number]= with =[new-line]=. This command replaces the line =[line-number]= (in the current file) with =[new-line]=.
Lean uses the snapshots to process the request efficiently. Lean uses the snapshots to process the request efficiently.
If =[line-number]= is greater than the total number of lines in the lean If =[line-number]= is greater than the total number of lines in the lean
buffer, then empty lines are introduced. The lines are indexed from 1. buffer, then empty lines are introduced. The lines are indexed from 1.
@ -39,7 +53,7 @@ INSERT [line-number]
[new-line] [new-line]
#+END_SRC #+END_SRC
This command inserts =[new-line]= before line =[line-number]=. This command inserts =[new-line]= (in the current file) before line =[line-number]=.
If =[line-number]= is greater than the total number of lines in the lean If =[line-number]= is greater than the total number of lines in the lean
buffer, then empty lines are introduced. The lines are indexed from 1. buffer, then empty lines are introduced. The lines are indexed from 1.
@ -49,7 +63,7 @@ buffer, then empty lines are introduced. The lines are indexed from 1.
REMOVE [line-number] REMOVE [line-number]
#+END_SRC #+END_SRC
Remove line =[line-number]=. The lines are indexed from 1. Remove line =[line-number]= (in the current file). The lines are indexed from 1.
If =[line-number]= is greater than the total number of lines in the lean If =[line-number]= is greater than the total number of lines in the lean
buffer, then the command is ignored. buffer, then the command is ignored.
@ -59,7 +73,8 @@ buffer, then the command is ignored.
INFO [line-number] INFO [line-number]
#+END_SRC #+END_SRC
This command extracts typing information associated with line =[line-number]=. This command extracts typing information associated with line
=[line-number]= (in the current file).
Lean produces a possible empty sequence of entries terminated with Lean produces a possible empty sequence of entries terminated with
#+BEGIN_SRC #+BEGIN_SRC
@ -108,3 +123,22 @@ num
-- ACK -- ACK
-- ENDINFO -- ENDINFO
#+END_SRC #+END_SRC
** Check line
As described above, several commands can be used to apply
modifications to opened/visited files. These modification reflect
modifications performed by the text editor. The command =CHECK= can be
used to double check whether the text editor and Lean have the "same
view" of the current file + modifications.
The following commands returns =-- OK= if the line =[line-number]= in
the current file is =[line]=. It returns =-- MISMATCH line out of
range=, if =[line-number]= is too big, and =-- MISMATCH expected
[lean-line]= when there is a mismatch, and Lean expects
=[line-number]= to be =[lean-line]=.
#+BEGIN_SRC
-- CHECK [line-number]
[line]
#+END_SRC

View file

@ -11,15 +11,77 @@ Author: Leonardo de Moura
#include "frontends/lean/parser.h" #include "frontends/lean/parser.h"
namespace lean { namespace lean {
server::server(environment const & env, io_state const & ios, unsigned num_threads): server::file::file(std::string const & fname):m_fname(fname), m_from(0) {}
m_env(env), m_options(ios.get_options()), m_ios(ios), m_num_threads(num_threads),
m_empty_snapshot(m_env, m_options), m_from(0) { void server::file::replace_line(unsigned linenum, std::string const & new_line) {
while (linenum >= m_lines.size())
m_lines.push_back("");
m_lines[linenum] = new_line;
if (linenum < m_from)
m_from = linenum;
} }
static std::string g_file("FILE"); void server::file::insert_line(unsigned linenum, std::string const & new_line) {
while (linenum >= m_lines.size())
m_lines.push_back("");
m_lines.push_back("");
lean_assert(m_lines.size() >= linenum+1);
unsigned i = m_lines.size();
while (i > linenum) {
--i;
m_lines[i] = m_lines[i-1];
}
m_lines[linenum] = new_line;
if (linenum < m_from)
m_from = linenum;
}
void server::file::remove_line(unsigned linenum) {
if (linenum >= m_lines.size())
return;
lean_assert(!m_lines.empty());
for (unsigned i = linenum; i < m_lines.size()-1; i++)
m_lines[i] = m_lines[i+1];
m_lines.pop_back();
if (linenum < m_from)
m_from = linenum;
}
/**
\brief Return index i <= m_snapshots.size() s.t.
* forall j < i, m_snapshots[j].m_line < line
* forall i <= j < m_snapshots.size(), m_snapshots[j].m_line >= line
*/
unsigned server::file::find(unsigned linenum) {
unsigned low = 0;
unsigned high = m_snapshots.size();
while (true) {
lean_assert(low <= high);
if (low == high)
return low;
unsigned mid = low + ((high - low)/2);
lean_assert(low <= mid && mid < high);
lean_assert(mid < m_snapshots.size());
snapshot const & s = m_snapshots[mid];
if (s.m_line < linenum) {
low = mid+1;
} else {
high = mid;
}
}
}
server::server(environment const & env, io_state const & ios, unsigned num_threads):
m_env(env), m_options(ios.get_options()), m_ios(ios), m_out(ios.get_regular_channel().get_stream()),
m_num_threads(num_threads), m_empty_snapshot(m_env, m_options) {
}
static std::string g_load("LOAD");
static std::string g_visit("VISIT");
static std::string g_replace("REPLACE"); static std::string g_replace("REPLACE");
static std::string g_insert("INSERT"); static std::string g_insert("INSERT");
static std::string g_remove("REMOVE"); static std::string g_remove("REMOVE");
static std::string g_check("CHECK");
static std::string g_info("INFO"); static std::string g_info("INFO");
static bool is_command(std::string const & cmd, std::string const & line) { static bool is_command(std::string const & cmd, std::string const & line) {
@ -40,112 +102,61 @@ static std::string & trim(std::string & s) {
return ltrim(rtrim(s)); return ltrim(rtrim(s));
} }
/**
\brief Return index i <= m_snapshots.size() s.t.
* forall j < i, m_snapshots[j].m_line < line
* forall i <= j < m_snapshots.size(), m_snapshots[j].m_line >= line
*/
unsigned server::find(unsigned linenum) {
unsigned low = 0;
unsigned high = m_snapshots.size();
while (true) {
lean_assert(low <= high);
if (low == high)
return low;
unsigned mid = low + ((high - low)/2);
lean_assert(low <= mid && mid < high);
lean_assert(mid < m_snapshots.size());
snapshot const & s = m_snapshots[mid];
if (s.m_line < linenum) {
low = mid+1;
} else {
high = mid;
}
}
}
void server::process_from(unsigned linenum) { void server::process_from(unsigned linenum) {
unsigned i = find(linenum); unsigned i = m_file->find(linenum);
m_snapshots.resize(i); m_file->m_snapshots.resize(i);
snapshot & s = i == 0 ? m_empty_snapshot : m_snapshots[i-1]; snapshot & s = i == 0 ? m_empty_snapshot : m_file->m_snapshots[i-1];
std::string block; std::string block;
lean_assert(s.m_line > 0); lean_assert(s.m_line > 0);
m_info.invalidate(s.m_line-1); m_file->m_info.invalidate(s.m_line-1);
for (unsigned j = s.m_line-1; j < m_lines.size(); j++) { for (unsigned j = s.m_line-1; j < m_file->m_lines.size(); j++) {
block += m_lines[j]; block += m_file->m_lines[j];
block += '\n'; block += '\n';
} }
std::istringstream strm(block); std::istringstream strm(block);
m_ios.set_options(s.m_options); m_ios.set_options(s.m_options);
parser p(s.m_env, m_ios, strm, m_fname.c_str(), false, 1, s.m_lds, s.m_eds, s.m_line, parser p(s.m_env, m_ios, strm, m_file->m_fname.c_str(), false, 1, s.m_lds, s.m_eds, s.m_line,
&m_snapshots, &m_info); &m_file->m_snapshots, &m_file->m_info);
p.set_cache(&m_cache); p.set_cache(&m_cache);
p(); p();
} }
void server::update() { void server::update() {
if (m_from == m_lines.size()) if (m_file->m_from == m_file->m_lines.size())
return; return;
process_from(m_from); process_from(m_file->m_from);
m_from = m_lines.size(); m_file->m_from = m_file->m_lines.size();
} }
void server::read_file(std::string const & fname) { void server::load_file(std::string const & fname) {
std::ifstream in(fname); std::ifstream in(fname);
if (in.bad() || in.fail()) { if (in.bad() || in.fail()) {
std::cout << "Error: failed to open file '" << fname << "'"; m_out << "-- ERROR failed to open file '" << fname << "'" << std::endl;
} else { } else {
m_fname = fname; m_file.reset(new file(fname));
m_lines.clear(); m_file_map.insert(mk_pair(fname, m_file));
for (std::string line; std::getline(in, line);) { for (std::string line; std::getline(in, line);) {
m_lines.push_back(line); m_file->m_lines.push_back(line);
} }
m_from = 0;
} }
} }
void server::replace_line(unsigned linenum, std::string const & new_line) { void server::visit_file(std::string const & fname) {
while (linenum >= m_lines.size()) auto it = m_file_map.find(fname);
m_lines.push_back(""); if (it == m_file_map.end())
m_lines[linenum] = new_line; load_file(fname);
if (linenum < m_from) else
m_from = linenum; m_file = it->second;
}
void server::insert_line(unsigned linenum, std::string const & new_line) {
while (linenum >= m_lines.size())
m_lines.push_back("");
m_lines.push_back("");
lean_assert(m_lines.size() >= linenum+1);
unsigned i = m_lines.size();
while (i > linenum) {
--i;
m_lines[i] = m_lines[i-1];
}
m_lines[linenum] = new_line;
if (linenum < m_from)
m_from = linenum;
}
void server::remove_line(unsigned linenum) {
if (linenum >= m_lines.size())
return;
lean_assert(!m_lines.empty());
for (unsigned i = linenum; i < m_lines.size()-1; i++)
m_lines[i] = m_lines[i+1];
m_lines.pop_back();
if (linenum < m_from)
m_from = linenum;
} }
void server::show_info(unsigned linenum) { void server::show_info(unsigned linenum) {
update(); update();
unsigned i = find(linenum); unsigned i = m_file->find(linenum);
environment const & env = i == 0 ? m_env : m_snapshots[i-1].m_env; environment const & env = i == 0 ? m_env : m_file->m_snapshots[i-1].m_env;
options const & o = i == 0 ? m_options : m_snapshots[i-1].m_options; options const & o = i == 0 ? m_options : m_file->m_snapshots[i-1].m_options;
m_ios.set_options(o); m_ios.set_options(o);
io_state_stream out(env, m_ios); io_state_stream out(env, m_ios);
m_info.display(out, linenum); m_file->m_info.display(out, linenum);
out << "-- ENDINFO" << endl; out << "-- ENDINFO" << endl;
} }
@ -164,23 +175,62 @@ unsigned server::get_linenum(std::string const & line, std::string const & cmd)
return r; return r;
} }
void server::check_file() {
if (!m_file)
throw exception("no file has been loaded/visited");
}
void server::replace_line(unsigned linenum, std::string const & new_line) {
check_file();
m_file->replace_line(linenum, new_line);
}
void server::insert_line(unsigned linenum, std::string const & new_line) {
check_file();
m_file->insert_line(linenum, new_line);
}
void server::remove_line(unsigned linenum) {
check_file();
m_file->remove_line(linenum);
}
void server::check_line(unsigned linenum, std::string const & line) {
check_file();
if (linenum >= m_file->m_lines.size()) {
m_out << "-- MISMATCH line out of range" << std::endl;
} else if (m_file->m_lines[linenum] != line) {
m_out << "-- MISMATCH expected " << m_file->m_lines[linenum] << std::endl;
} else {
m_out << "-- OK" << std::endl;
}
}
bool server::operator()(std::istream & in) { bool server::operator()(std::istream & in) {
for (std::string line; std::getline(in, line);) { for (std::string line; std::getline(in, line);) {
try { try {
if (is_command(g_file, line)) { if (is_command(g_load, line)) {
std::string fname = line.substr(g_file.size()); std::string fname = line.substr(g_load.size());
trim(fname); trim(fname);
read_file(fname); load_file(fname);
} else if (is_command(g_visit, line)) {
std::string fname = line.substr(g_visit.size());
trim(fname);
visit_file(fname);
} else if (is_command(g_check, line)) {
unsigned linenum = get_linenum(line, g_check);
read_line(in, line);
check_line(linenum-1, line);
} else if (is_command(g_replace, line)) { } else if (is_command(g_replace, line)) {
unsigned linenum = get_linenum(line, g_replace); unsigned linenum = get_linenum(line, g_replace);
read_line(in, line); read_line(in, line);
replace_line(linenum-1, line); replace_line(linenum-1, line);
} else if (is_command(g_insert, line)) { } else if (is_command(g_insert, line)) {
unsigned linenum = get_linenum(line, g_replace); unsigned linenum = get_linenum(line, g_insert);
read_line(in, line); read_line(in, line);
insert_line(linenum-1, line); insert_line(linenum-1, line);
} else if (is_command(g_remove, line)) { } else if (is_command(g_remove, line)) {
unsigned linenum = get_linenum(line, g_replace); unsigned linenum = get_linenum(line, g_remove);
remove_line(linenum-1); remove_line(linenum-1);
} else if (is_command(g_info, line)) { } else if (is_command(g_info, line)) {
unsigned linenum = get_linenum(line, g_info); unsigned linenum = get_linenum(line, g_info);
@ -189,7 +239,7 @@ bool server::operator()(std::istream & in) {
throw exception(sstream() << "unexpected command line: " << line); throw exception(sstream() << "unexpected command line: " << line);
} }
} catch (exception & ex) { } catch (exception & ex) {
std::cout << "Error: " << ex.what() << "\n"; m_out << "-- ERROR " << ex.what() << std::endl;
} }
} }
return true; return true;

View file

@ -7,6 +7,7 @@ Author: Leonardo de Moura
#pragma once #pragma once
#include <vector> #include <vector>
#include <string> #include <string>
#include <unordered_map>
#include "library/definitions_cache.h" #include "library/definitions_cache.h"
#include "frontends/lean/parser.h" #include "frontends/lean/parser.h"
#include "frontends/lean/info_manager.h" #include "frontends/lean/info_manager.h"
@ -17,22 +18,38 @@ namespace lean {
external processes. external processes.
*/ */
class server { class server {
std::vector<std::string> m_lines; struct file {
snapshot_vector m_snapshots; std::string m_fname;
info_manager m_info; std::vector<std::string> m_lines;
snapshot_vector m_snapshots;
info_manager m_info;
unsigned m_from; // mark the first modified line that was not processed yet.
file(std::string const & fname);
unsigned find(unsigned linenum);
void replace_line(unsigned linenum, std::string const & new_line);
void insert_line(unsigned linenum, std::string const & new_line);
void remove_line(unsigned linenum);
};
typedef std::shared_ptr<file> file_ptr;
typedef std::unordered_map<std::string, file_ptr> file_map;
file_map m_file_map;
file_ptr m_file;
environment m_env; environment m_env;
options m_options; options m_options;
io_state m_ios; io_state m_ios;
std::ostream & m_out;
unsigned m_num_threads; unsigned m_num_threads;
snapshot m_empty_snapshot; snapshot m_empty_snapshot;
std::string m_fname;
unsigned m_from;
definitions_cache m_cache; definitions_cache m_cache;
void read_file(std::string const & fname); void load_file(std::string const & fname);
void visit_file(std::string const & fname);
void check_file();
void replace_line(unsigned linenum, std::string const & new_line); void replace_line(unsigned linenum, std::string const & new_line);
void insert_line(unsigned linenum, std::string const & new_line); void insert_line(unsigned linenum, std::string const & new_line);
void remove_line(unsigned linenum); void remove_line(unsigned linenum);
void check_line(unsigned linenum, std::string const & line);
void show_info(unsigned linenum); void show_info(unsigned linenum);
void process_from(unsigned linenum); void process_from(unsigned linenum);
unsigned find(unsigned linenum); unsigned find(unsigned linenum);