/* Copyright (c) 2016 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Gabriel Ebner, Leonardo de Moura, Sebastian Ullrich */ #if defined(LEAN_SERVER) #include #include #include #include #include #include "frontends/lean/parser.h" #include "library/module.h" #include "shell/server.h" #include "util/sexpr/option_declarations.h" #include "frontends/lean/util.h" #include "library/protected.h" #include "library/scoped_ext.h" #include "util/bitap_fuzzy_search.h" #include "library/type_context.h" #include "kernel/instantiate.h" #ifndef LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS #define LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS 100 #endif #define LEAN_FUZZY_MAX_ERRORS 3 #define LEAN_FUZZY_MAX_ERRORS_FACTOR 3 #define LEAN_COMPLETE_CONSUME_IMPLICIT true // lean will add metavariables for implicit arguments in serialize_decl() namespace lean { static name * g_auto_completion_max_results = nullptr; unsigned get_auto_completion_max_results(options const & o) { return o.get_unsigned(*g_auto_completion_max_results, LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS); } class null_message_stream : public message_stream { public: void report(message const &) override {} }; server::server(unsigned num_threads, environment const & initial_env, io_state const & ios) : m_num_threads(num_threads), m_initial_env(initial_env), m_ios(ios) { m_ios.set_regular_channel(std::make_shared()); m_ios.set_diagnostic_channel(std::make_shared()); m_ios.set_message_channel(std::make_shared()); } server::~server() { } void server::run() { scope_global_ios scoped_ios(m_ios); /* Leo: we use std::setlocale to make sure decimal period is displayed as ".". We added this hack because the json library code used for ensuring this property was crashing when compiling Lean on Windows with mingw. */ #if !defined(LEAN_EMSCRIPTEN) std::setlocale(LC_NUMERIC, "C"); #endif while (true) { try { std::string req_string; std::getline(std::cin, req_string); if (std::cin.eof()) return; json req = json::parse(req_string); json res = handle_request(req); std::cout << res << std::endl; } catch (std::exception & ex) { json res; res["response"] = "error"; res["message"] = ex.what(); std::cout << res << std::endl; } } } json server::handle_request(json const & req) { std::string command = req["command"]; if (command == "sync") { return handle_sync(req); } else if (command == "check") { return handle_check(req); } else if (command == "complete") { return handle_complete(req); } else if (command == "show_goal") { return handle_show_goal(req); } json res; res["response"] = "error"; res["message"] = "unknown command"; return res; } static optional diff(std::string a, std::string b) { std::istringstream in_a(a), in_b(b); for (unsigned line = 1;; line++) { if (in_a.eof() && in_b.eof()) return optional(); if (in_a.eof() || in_b.eof()) return optional(line, 0); std::string line_a, line_b; std::getline(in_a, line_a); std::getline(in_b, line_b); // TODO(gabriel): return column as well if (line_a != line_b) return optional(line, 0); } } json server::handle_sync(json const & req) { std::string new_file_name = req["file_name"]; std::string new_content = req["content"]; json res; res["response"] = "ok"; if (m_file_name != new_file_name) { m_file_name = new_file_name; m_content = new_content; m_only_checked_until = optional(0, 0); res["message"] = "new file name, reloading"; } else { if (auto diff_pos = diff(m_content, new_content)) { m_content = new_content; // TODO(gabriel): implement min on pos_info if (m_only_checked_until && m_only_checked_until->first < diff_pos->first) { // we have not yet checked up to the differing position } else { m_only_checked_until = diff_pos; } res["message"] = "file partially invalidated"; } else { res["message"] = "no file changes"; } } return res; } json server::handle_check(json const &) { if (imports_have_changed(m_checked_env)) m_only_checked_until = optional(0, 0); if (m_only_checked_until) { // keep all snapshots before the change but the last one (which may belong to the command that's being changed) auto it = m_snapshots.begin(); while (it != m_snapshots.end() && it->m_pos < *m_only_checked_until) it++; if (it != m_snapshots.begin()) it--; m_snapshots.erase(it, m_snapshots.end()); std::istringstream in(m_content); bool use_exceptions = false; optional base_dir; parser p(m_initial_env, m_ios, in, m_file_name.c_str(), base_dir, use_exceptions, m_num_threads, m_snapshots.empty() ? nullptr : &m_snapshots.back(), &m_snapshots); // TODO(gabriel): definition caches? m_parsed_ok = p(); m_only_checked_until = optional(); m_checked_env = p.env(); m_messages = p.get_messages(); } json res; res["response"] = "ok"; res["is_ok"] = m_parsed_ok; std::list json_messages; for_each(m_messages, [&](message const & msg) { json_messages.push_front(json_of_message(msg)); }); res["messages"] = json_messages; return res; } snapshot const * server::get_closest_snapshot(unsigned line) { snapshot const * ret = m_snapshots.size() ? &m_snapshots.front() : nullptr; for (snapshot const & snap : m_snapshots) { if (snap.m_pos.first <= line) ret = &snap; } return ret; } /** \brief Return an (atomic) name if \c n can be referenced by this atomic name in the given environment. */ optional is_essentially_atomic(environment const & env, name const & n) { if (n.is_atomic()) return optional(n); list const & ns_list = get_namespaces(env); for (name const & ns : ns_list) { if (is_prefix_of(ns, n)) { auto n_prime = n.replace_prefix(ns, name()); if (n_prime.is_atomic() && !is_protected(env, n)) return optional(n_prime); break; } } if (auto it = is_uniquely_aliased(env, n)) if (it->is_atomic()) return it; return optional(); } unsigned get_fuzzy_match_max_errors(unsigned prefix_sz) { unsigned r = (prefix_sz / LEAN_FUZZY_MAX_ERRORS_FACTOR); if (r > LEAN_FUZZY_MAX_ERRORS) return LEAN_FUZZY_MAX_ERRORS; return r; } optional exact_prefix_match(environment const & env, std::string const & pattern, declaration const & d) { if (auto it = is_essentially_atomic(env, d.get_name())) { std::string it_str = it->to_string(); // if pattern "perfectly" matches beginning of declaration name, we just display d on the top of the list if (it_str.compare(0, pattern.size(), pattern) == 0) return it; } else { std::string d_str = d.get_name().to_string(); if (d_str.compare(0, pattern.size(), pattern) == 0) return optional(d.get_name()); } return optional(); } json server::serialize_decl(name const & short_name, name const & long_name, environment const & env, options const & o) { declaration const & d = env.get(long_name); type_context tc(env); io_state_stream out = regular(env, m_ios, tc).update_options(o); expr type = d.get_type(); if (LEAN_COMPLETE_CONSUME_IMPLICIT) { while (true) { if (!is_pi(type)) break; if (!binding_info(type).is_implicit() && !binding_info(type).is_inst_implicit()) break; std::string q("?"); q += binding_name(type).to_string(); expr m = mk_constant(name(q.c_str())); type = instantiate(binding_body(type), m); } } json completion; completion["text"] = short_name.to_string(); std::ostringstream ss; ss << mk_pair(flatten(out.get_formatter()(type)), o); completion["type"] = ss.str(); return completion; } json server::serialize_decl(name const & d, environment const & env, options const & o) { // using namespace override resolution rule list const & ns_list = get_namespaces(env); for (name const & ns : ns_list) { name new_d = d.replace_prefix(ns, name()); if (new_d != d && !new_d.is_anonymous() && (!new_d.is_atomic() || !is_protected(env, d))) { return serialize_decl(new_d, d, env, o); } } // if the alias is unique use it if (auto it = is_uniquely_aliased(env, d)) { return serialize_decl(*it, d, env, o); } else { return serialize_decl(d, d, env, o); } } json server::handle_complete(json const & req) { std::string pattern = req["pattern"]; unsigned line = req["line"]; std::vector completions; if (!m_snapshots.size()) { // should only happen when imports have been touched handle_check({}); } if (snapshot const * snap = get_closest_snapshot(line)) { environment const & env = snap->m_env; options const & opts = snap->m_options; unsigned max_results = get_auto_completion_max_results(opts); unsigned max_errors = get_fuzzy_match_max_errors(pattern.size()); std::vector> exact_matches; std::vector> selected; bitap_fuzzy_search matcher(pattern, max_errors); env.for_each_declaration([&](declaration const & d) { if (is_projection(env, d.get_name())) return; if (auto it = exact_prefix_match(env, pattern, d)) { exact_matches.emplace_back(*it, d.get_name()); } else { std::string text = d.get_name().to_string(); if (matcher.match(text)) selected.emplace_back(text, d.get_name()); } }); unsigned num_results = 0; if (!exact_matches.empty()) { std::sort(exact_matches.begin(), exact_matches.end(), [](pair const & p1, pair const & p2) { return p1.first.size() < p2.first.size(); }); for (pair const & p : exact_matches) { completions.push_back(serialize_decl(p.first, p.second, env, opts)); num_results++; if (num_results >= max_results) break; } } unsigned sz = selected.size(); if (sz == 1) { completions.push_back(serialize_decl(selected[0].second, env, opts)); } else if (sz > 1) { std::vector> next_selected; for (unsigned k = 0; k <= max_errors && num_results < max_results; k++) { bitap_fuzzy_search matcher(pattern, k); for (auto const & s : selected) { if (matcher.match(s.first)) { completions.push_back(serialize_decl(s.second, env, opts)); num_results++; if (num_results >= max_results) break; } else { next_selected.push_back(s); } } std::swap(selected, next_selected); next_selected.clear(); } } } json res; res["response"] = "ok"; res["completions"] = completions; return res; } json server::handle_show_goal(json const &req) { pos_info pos(req["line"], req["col"]); if (!m_snapshots.size()) handle_check({}); snapshot * snap = nullptr; for (auto & s : m_snapshots) { if (s.m_pos.first < pos.first) snap = &s; } std::istringstream in(m_content); bool use_exceptions = false; optional base_dir; parser p(m_initial_env, m_ios, in, m_file_name.c_str(), base_dir, use_exceptions, m_num_threads, snap); p.enable_show_goal(pos); try { p(); } catch (show_goal_exception & ex) { json res; res["response"] = "ok"; res["line"] = ex.m_pos.first; res["col"] = ex.m_pos.second; std::stringstream buf; buf << ex.m_state.pp(); res["state"] = buf.str(); return res; } json res; res["response"] = "error"; res["message"] = "could not find goal"; return res; } void initialize_server() { g_auto_completion_max_results = new name{"auto_completion", "max_results"}; register_unsigned_option(*g_auto_completion_max_results, LEAN_DEFAULT_AUTO_COMPLETION_MAX_RESULTS, "(auto-completion) maximum number of results returned"); } void finalize_server() { delete g_auto_completion_max_results; } } #endif