feat(frontends/lean/server): add VISIT and CHECK commands
Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
This commit is contained in:
parent
71583ba9c8
commit
faf2795a7b
3 changed files with 199 additions and 98 deletions
|
@ -13,13 +13,27 @@ to the standard input.
|
|||
** Load file
|
||||
|
||||
#+BEGIN_SRC
|
||||
FILE [file-name]
|
||||
LOAD [file-name]
|
||||
#+END_SRC
|
||||
|
||||
This command loads the Lean file named =[file-name]=.
|
||||
Lean will create a "snapshot" (aka backtracking point) after each
|
||||
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
|
||||
|
||||
#+BEGIN_SRC
|
||||
|
@ -27,7 +41,7 @@ REPLACE [line-number]
|
|||
[new-line]
|
||||
#+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.
|
||||
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.
|
||||
|
@ -39,7 +53,7 @@ INSERT [line-number]
|
|||
[new-line]
|
||||
#+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
|
||||
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]
|
||||
#+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
|
||||
buffer, then the command is ignored.
|
||||
|
||||
|
@ -59,7 +73,8 @@ buffer, then the command is ignored.
|
|||
INFO [line-number]
|
||||
#+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
|
||||
|
||||
#+BEGIN_SRC
|
||||
|
@ -108,3 +123,22 @@ num
|
|||
-- ACK
|
||||
-- ENDINFO
|
||||
#+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
|
||||
|
|
|
@ -11,15 +11,77 @@ Author: Leonardo de Moura
|
|||
#include "frontends/lean/parser.h"
|
||||
|
||||
namespace lean {
|
||||
server::server(environment const & env, io_state const & ios, unsigned num_threads):
|
||||
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) {
|
||||
server::file::file(std::string const & fname):m_fname(fname), 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_insert("INSERT");
|
||||
static std::string g_remove("REMOVE");
|
||||
static std::string g_check("CHECK");
|
||||
static std::string g_info("INFO");
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
\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) {
|
||||
unsigned i = find(linenum);
|
||||
m_snapshots.resize(i);
|
||||
snapshot & s = i == 0 ? m_empty_snapshot : m_snapshots[i-1];
|
||||
unsigned i = m_file->find(linenum);
|
||||
m_file->m_snapshots.resize(i);
|
||||
snapshot & s = i == 0 ? m_empty_snapshot : m_file->m_snapshots[i-1];
|
||||
std::string block;
|
||||
lean_assert(s.m_line > 0);
|
||||
m_info.invalidate(s.m_line-1);
|
||||
for (unsigned j = s.m_line-1; j < m_lines.size(); j++) {
|
||||
block += m_lines[j];
|
||||
m_file->m_info.invalidate(s.m_line-1);
|
||||
for (unsigned j = s.m_line-1; j < m_file->m_lines.size(); j++) {
|
||||
block += m_file->m_lines[j];
|
||||
block += '\n';
|
||||
}
|
||||
std::istringstream strm(block);
|
||||
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,
|
||||
&m_snapshots, &m_info);
|
||||
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_file->m_snapshots, &m_file->m_info);
|
||||
p.set_cache(&m_cache);
|
||||
p();
|
||||
}
|
||||
|
||||
void server::update() {
|
||||
if (m_from == m_lines.size())
|
||||
if (m_file->m_from == m_file->m_lines.size())
|
||||
return;
|
||||
process_from(m_from);
|
||||
m_from = m_lines.size();
|
||||
process_from(m_file->m_from);
|
||||
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);
|
||||
if (in.bad() || in.fail()) {
|
||||
std::cout << "Error: failed to open file '" << fname << "'";
|
||||
m_out << "-- ERROR failed to open file '" << fname << "'" << std::endl;
|
||||
} else {
|
||||
m_fname = fname;
|
||||
m_lines.clear();
|
||||
m_file.reset(new file(fname));
|
||||
m_file_map.insert(mk_pair(fname, m_file));
|
||||
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) {
|
||||
while (linenum >= m_lines.size())
|
||||
m_lines.push_back("");
|
||||
m_lines[linenum] = new_line;
|
||||
if (linenum < m_from)
|
||||
m_from = linenum;
|
||||
}
|
||||
|
||||
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::visit_file(std::string const & fname) {
|
||||
auto it = m_file_map.find(fname);
|
||||
if (it == m_file_map.end())
|
||||
load_file(fname);
|
||||
else
|
||||
m_file = it->second;
|
||||
}
|
||||
|
||||
void server::show_info(unsigned linenum) {
|
||||
update();
|
||||
unsigned i = find(linenum);
|
||||
environment const & env = i == 0 ? m_env : m_snapshots[i-1].m_env;
|
||||
options const & o = i == 0 ? m_options : m_snapshots[i-1].m_options;
|
||||
unsigned i = m_file->find(linenum);
|
||||
environment const & env = i == 0 ? m_env : m_file->m_snapshots[i-1].m_env;
|
||||
options const & o = i == 0 ? m_options : m_file->m_snapshots[i-1].m_options;
|
||||
m_ios.set_options(o);
|
||||
io_state_stream out(env, m_ios);
|
||||
m_info.display(out, linenum);
|
||||
m_file->m_info.display(out, linenum);
|
||||
out << "-- ENDINFO" << endl;
|
||||
}
|
||||
|
||||
|
@ -164,23 +175,62 @@ unsigned server::get_linenum(std::string const & line, std::string const & cmd)
|
|||
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) {
|
||||
for (std::string line; std::getline(in, line);) {
|
||||
try {
|
||||
if (is_command(g_file, line)) {
|
||||
std::string fname = line.substr(g_file.size());
|
||||
if (is_command(g_load, line)) {
|
||||
std::string fname = line.substr(g_load.size());
|
||||
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)) {
|
||||
unsigned linenum = get_linenum(line, g_replace);
|
||||
read_line(in, line);
|
||||
replace_line(linenum-1, 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);
|
||||
insert_line(linenum-1, 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);
|
||||
} else if (is_command(g_info, line)) {
|
||||
unsigned linenum = get_linenum(line, g_info);
|
||||
|
@ -189,7 +239,7 @@ bool server::operator()(std::istream & in) {
|
|||
throw exception(sstream() << "unexpected command line: " << line);
|
||||
}
|
||||
} catch (exception & ex) {
|
||||
std::cout << "Error: " << ex.what() << "\n";
|
||||
m_out << "-- ERROR " << ex.what() << std::endl;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -7,6 +7,7 @@ Author: Leonardo de Moura
|
|||
#pragma once
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include "library/definitions_cache.h"
|
||||
#include "frontends/lean/parser.h"
|
||||
#include "frontends/lean/info_manager.h"
|
||||
|
@ -17,22 +18,38 @@ namespace lean {
|
|||
external processes.
|
||||
*/
|
||||
class server {
|
||||
std::vector<std::string> m_lines;
|
||||
snapshot_vector m_snapshots;
|
||||
info_manager m_info;
|
||||
struct file {
|
||||
std::string m_fname;
|
||||
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;
|
||||
options m_options;
|
||||
io_state m_ios;
|
||||
std::ostream & m_out;
|
||||
unsigned m_num_threads;
|
||||
snapshot m_empty_snapshot;
|
||||
std::string m_fname;
|
||||
unsigned m_from;
|
||||
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 insert_line(unsigned linenum, std::string const & new_line);
|
||||
void remove_line(unsigned linenum);
|
||||
void check_line(unsigned linenum, std::string const & line);
|
||||
void show_info(unsigned linenum);
|
||||
void process_from(unsigned linenum);
|
||||
unsigned find(unsigned linenum);
|
||||
|
|
Loading…
Reference in a new issue