On Mon, Jul 24, 2017 at 10:05 PM, David Malcolm <dmalc...@redhat.com> wrote: > This patch adds an lsp::server abstract base class for implementing > servers for the Language Server Protocol: > https://github.com/Microsoft/language-server-protocol > > along with supporting classes mirroring those from the protocol > description. > > The public specification of the protocol uses CamelCase, and so these > classes use CamelCase, to mirror the protocol definition. > > Only a small subset of the protocol is implemented, enough for > a proof-of-concept. > > Ideally much/all of this would be autogenerated from the > protocol definition. > > The patch also implements an ::lsp::jsonrpc_server subclass of > ::jsonrpc::server, handling the marshalling from JSON-RPC to > an lsp::server instance, expressing them as vfunc calls. > > gcc/ChangeLog: > * Makefile.in (OBJS): Add lsp.o. > * lsp.c: New file.
New files should use .cc extension (we're using C++ now). Richard. > * lsp.h: New file. > * selftest-run-tests.c (selftest::run_tests): Call > selftest::lsp_c_tests. > * selftest.h (selftest::lsp_c_tests): New decl. > --- > gcc/Makefile.in | 1 + > gcc/lsp.c | 291 > +++++++++++++++++++++++++++++++++++++++++++++++ > gcc/lsp.h | 210 ++++++++++++++++++++++++++++++++++ > gcc/selftest-run-tests.c | 1 + > gcc/selftest.h | 1 + > 5 files changed, 504 insertions(+) > create mode 100644 gcc/lsp.c > create mode 100644 gcc/lsp.h > > diff --git a/gcc/Makefile.in b/gcc/Makefile.in > index 1f9050c..e5120c2 100644 > --- a/gcc/Makefile.in > +++ b/gcc/Makefile.in > @@ -1382,6 +1382,7 @@ OBJS = \ > loop-iv.o \ > loop-unroll.o \ > lower-subreg.o \ > + lsp.o \ > lra.o \ > lra-assigns.o \ > lra-coalesce.o \ > diff --git a/gcc/lsp.c b/gcc/lsp.c > new file mode 100644 > index 0000000..3b79794 > --- /dev/null > +++ b/gcc/lsp.c > @@ -0,0 +1,291 @@ > +/* Language Server Protocol implementation. > + Copyright (C) 2017 Free Software Foundation, Inc. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#include "config.h" > +#include "system.h" > +#include "coretypes.h" > +#include "json.h" > +#include "http-server.h" > +#include "json-rpc.h" > +#include "lsp.h" > +#include "selftest.h" > + > +using namespace jsonrpc; > +using namespace lsp; > + > +// TODO: autogenerate the interface binding/marshalling/demarshalling code > +// from an interface description. > + > +#define SET_VALUE(LHS, VALUE, NAME) \ > + do { \ > + if (!(VALUE)->get_value_by_key ((NAME), (LHS), out_err)) \ > + return result; \ > + } while (0) > + > +#define SET_NUMBER(LHS, VALUE, NAME) \ > + do { \ > + if (!(VALUE)->get_int_by_key ((NAME), (LHS), out_err)) \ > + return result; \ > + } while (0) > + > +#define SET_STRING(LHS, VALUE, NAME) \ > + do { \ > + if (!(VALUE)->get_string_by_key ((NAME), (LHS), out_err)) \ > + return result; \ > + } while (0) > + > +Position > +Position::from_json (const json::value *params, > + char *&out_err) > +{ > + Position result; > + SET_NUMBER (result.line, params, "line"); > + SET_NUMBER (result.character, params, "character"); > + return result; > +} > + > +TextDocumentIdentifier > +TextDocumentIdentifier::from_json (const json::value *params, > + char *&out_err) > +{ > + TextDocumentIdentifier result; > + SET_STRING (result.uri, params, "uri"); > + return result; > +} > + > +TextDocumentItem > +TextDocumentItem::from_json (const json::value *params, > + char *&out_err) > +{ > + TextDocumentItem result; > + SET_STRING (result.uri, params, "uri"); > + SET_STRING (result.languageId, params, "languageId"); > + SET_NUMBER (result.version, params, "version"); > + SET_STRING (result.text, params, "text"); > + return result; > +} > + > +DidOpenTextDocumentParams > +DidOpenTextDocumentParams::from_json (const json::value *params, > + char *&out_err) > +{ > + DidOpenTextDocumentParams result; > + // FIXME: error-handling > + const json::value *text_document; > + SET_VALUE (text_document, params, "textDocument"); > + result.textDocument > + = TextDocumentItem::from_json (text_document, out_err); > + return result; > +} > + > +DidChangeTextDocumentParams > +DidChangeTextDocumentParams::from_json (const json::value */*params*/, > + char *&/*out_err*/) > +{ > + DidChangeTextDocumentParams result; > + > + // FIXME > + return result; > +} > + > +TextDocumentPositionParams > +TextDocumentPositionParams::from_json (const json::value *params, > + char *&out_err) > +{ > + TextDocumentPositionParams result; > + const json::value *text_document; > + const json::value *position; > + SET_VALUE (text_document, params, "textDocument"); > + SET_VALUE (position, params, "position"); > + result.textDocument > + = TextDocumentIdentifier::from_json (text_document, out_err); > + result.position > + = Position::from_json (position, out_err); > + return result; > +} > + > +/* struct Position. */ > + > +json::value * > +Position::to_json () const > +{ > + json::object *result = new json::object (); > + result->set ("line", new json::number (line)); > + result->set ("character", new json::number (character)); > + return result; > +} > + > +/* struct Range. */ > + > +json::value * > +Range::to_json () const > +{ > + json::object *result = new json::object (); > + result->set ("start", start.to_json ()); > + result->set ("end", end.to_json ()); > + return result; > +} > + > +/* struct Location. */ > + > +json::value * > +Location::to_json () const > +{ > + json::object *result = new json::object (); > + result->set ("uri", new json::string (uri)); > + result->set ("range", range.to_json ()); > + return result; > +} > + > +/* class lsp::jsonrpc_server : public ::jsonrpc::server. */ > + > +json::value * > +lsp::jsonrpc_server::dispatch (const char *method, const json::value *params, > + const json::value *id) > +{ > + if (0 == strcmp (method, "initialize")) > + return do_initialize (id, params); > + if (0 == strcmp (method, "textDocument/didOpen")) > + return do_text_document_did_open (params); > + if (0 == strcmp (method, "textDocument/didChange")) > + return do_text_document_did_change (params); > + if (0 == strcmp (method, "textDocument/definition")) > + return do_text_document_definition (params); > + return make_method_not_found (id, method); > +} > + > +json::value * > +lsp::jsonrpc_server::do_initialize (const json::value *id, > + const json::value */*params*/) > +{ > + // FIXME: for now, ignore params > + > + json::object *server_caps = new json::object (); > + json::object *result = new json::object (); > + result->set ("capabilities", server_caps); > + return make_success (id, result); > +} > + > +static json::value * > +make_invalid_params_and_free_msg (const json::value *id, char *msg) > +{ > + json::value *err = make_invalid_params (id, msg); > + free (msg); > + return err; > +} > + > +json::value * > +lsp::jsonrpc_server::do_text_document_did_open (const json::value *params) > +{ > + char *err = NULL; > + DidOpenTextDocumentParams p > + = DidOpenTextDocumentParams::from_json (params, err); > + if (err) > + return make_invalid_params_and_free_msg (NULL, err); // though we ought > not to return non-NULL for a notification > + > + m_inner.do_text_document_did_open (p); > + return NULL; // notification, so no response > +} > + > +json::value * > +lsp::jsonrpc_server::do_text_document_did_change (const json::value *params) > +{ > + char *err = NULL; > + DidChangeTextDocumentParams p > + = DidChangeTextDocumentParams::from_json (params, err); > + if (err) > + return make_invalid_params_and_free_msg (NULL, err); // though we ought > not to return non-NULL for a notification > + > + m_inner.do_text_document_did_change (p); > + > + return NULL; // notification, so no response > +} > + > +json::value * > +lsp::jsonrpc_server::do_text_document_definition (const json::value *params) > +{ > + char *err = NULL; > + TextDocumentPositionParams p > + = TextDocumentPositionParams::from_json (params, err); > + if (err) > + return make_invalid_params_and_free_msg (NULL, err); > + > + auto_vec<Location> out; > + m_inner.do_text_document_definition (p, out); > + > + json::array *result = new json::array (); > + unsigned i; > + Location *loc; > + FOR_EACH_VEC_ELT (out, i, loc) > + result->append (loc->to_json ()); > + return result; > +} > + > +#if CHECKING_P > + > +namespace selftest { > + > +/* Selftests. */ > + > +static void > +test_simple () > +{ > + noop_server noop; > + lsp::jsonrpc_server js (false, noop); > + const char *init_request > + = ("{\"jsonrpc\": \"2.0\", \"method\": \"initialize\"," > + " \"params\": [42, 23], \"id\": 1}"); // FIXME > + json::value *init_response = js.handle_request_string (init_request); > + //const json::value *result = assert_is_success (response, 1); > + //ASSERT_EQ (19, result->as_number ()->get ()); > + delete init_response; > + > + const char *did_open_note > + = ("{\"jsonrpc\": \"2.0\", \"method\": \"textDocument/didOpen\"," > + " \"params\": {" > + " \"textDocument\": " > + " { \"uri\": \"DocumentUri goes here\"," > + " \"languageId\": \"c++\"," > + " \"version\": 0," > + " \"text\": \"/* Initial content. */\"}}}"); > + json::value *did_open_response = js.handle_request_string (did_open_note); > + delete did_open_response; > + > + const char *did_change_note > + = ("{\"jsonrpc\": \"2.0\", \"method\": \"textDocument/didChange\"," > + " \"params\": {" > + " \"textDocument\": {\"version\": 1}," > + " \"contentChanges\": [{\"text\": \"/* Hello world. */\"}]" > + "}}"); // FIXME > + json::value *did_change_response = js.handle_request_string > (did_change_note); > + delete did_change_response; > +} > + > + > +/* Run all of the selftests within this file. */ > + > +void > +lsp_c_tests () > +{ > + test_simple (); > +} > + > +} // namespace selftest > + > +#endif /* #if CHECKING_P */ > diff --git a/gcc/lsp.h b/gcc/lsp.h > new file mode 100644 > index 0000000..d264cbf > --- /dev/null > +++ b/gcc/lsp.h > @@ -0,0 +1,210 @@ > +/* Language Server Protocol implementation. > + Copyright (C) 2017 Free Software Foundation, Inc. > + > +This file is part of GCC. > + > +GCC is free software; you can redistribute it and/or modify it under > +the terms of the GNU General Public License as published by the Free > +Software Foundation; either version 3, or (at your option) any later > +version. > + > +GCC is distributed in the hope that it will be useful, but WITHOUT ANY > +WARRANTY; without even the implied warranty of MERCHANTABILITY or > +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > +for more details. > + > +You should have received a copy of the GNU General Public License > +along with GCC; see the file COPYING3. If not see > +<http://www.gnu.org/licenses/>. */ > + > +#ifndef GCC_LSP_H > +#define GCC_LSP_H > + > +namespace lsp { > + > +typedef const char *DocumentUri; > + > +/* Interfaces from the protocol specification (which uses camel case). */ > + > +/* Note that LSP uses 0-based lines and characters, whereas GCC uses > + 1-based lines and columns. */ > + > +struct Position > +{ > + Position () > + : line (0), character (0) {} > + > + static Position from_json (const json::value *params, > + char *&out_err); > + json::value *to_json () const; > + > + int line; > + int character; > +}; > + > +struct Range > +{ > + Range () > + : start (), end () {} > + > + json::value *to_json () const; > + > + Position start; > + Position end; > +}; > + > +struct Location > +{ > + Location () > + : uri (NULL), range () {} > + > + json::value *to_json () const; > + > + DocumentUri uri; > + Range range; > +}; > + > +// Exceptions would be nicer than passing around the out_err > + > +// TODO: autogenerate the interface binding/marshalling/demarshalling code > +// from an interface description. > + > +struct TextDocumentIdentifier > +{ > + TextDocumentIdentifier () > + : uri (NULL) {} > + > + static TextDocumentIdentifier from_json (const json::value *params, > + char *&out_err); > + > + DocumentUri uri; > +}; > + > +struct TextDocumentItem > +{ > + TextDocumentItem () > + : uri (NULL), languageId (NULL), version (0), text (NULL) > + {} > + > + static TextDocumentItem from_json (const json::value *params, > + char *&out_err); > + > + DocumentUri uri; > + const char *languageId; > + int version; > + const char *text; > +}; > + > +struct DidOpenTextDocumentParams > +{ > + DidOpenTextDocumentParams () > + : textDocument () {} > + > + static DidOpenTextDocumentParams from_json (const json::value *params, > + char *&out_err); > + > + TextDocumentItem textDocument; > +}; > + > +struct DidChangeTextDocumentParams > +{ > + public: > + static DidChangeTextDocumentParams from_json (const json::value *params, > + char *&out_err); > + > + private: > +#if 0 > + VersionedTextDocumentIdentifier textDocument; > + auto_vec<TextDocumentContentChangeEvent> contentChanges; > +#endif > +}; > + > +struct TextDocumentPositionParams > +{ > + TextDocumentPositionParams () > + : textDocument (), position () {} > + > + static TextDocumentPositionParams from_json (const json::value *params, > + char *&out_err); > + > + TextDocumentIdentifier textDocument; > + Position position; > +}; > + > +/* An abstract base class for implementing the LSP as vfunc calls, > + avoiding dealing with JSON. */ > + > +class server > +{ > + public: > + virtual ~server () {} > + > + virtual void > + do_text_document_did_open (const DidOpenTextDocumentParams &p) = 0; > + > + virtual void > + do_text_document_did_change (const DidChangeTextDocumentParams &p) = 0; > + > + virtual void > + do_text_document_definition (const TextDocumentPositionParams &p, > + vec<Location> &out) = 0; > +}; > + > +/* A concrete subclass of lsp::server that implements everything as a no-op. > */ > + > +class noop_server : public server > +{ > + void > + do_text_document_did_open (const DidOpenTextDocumentParams &) OVERRIDE > + { > + // no-op > + } > + > + void > + do_text_document_did_change (const DidChangeTextDocumentParams &) OVERRIDE > + { > + // no-op > + } > + > + void > + do_text_document_definition (const TextDocumentPositionParams &, > + vec<Location> &) OVERRIDE > + { > + // no-op > + } > +}; > + > +/* A jsonrpc::server subclass that decodes incoming JSON-RPC requests > + and dispatches them to an lsp::server instance as vfunc calls, > + marshalling the inputs/outputs to/from JSON objects. */ > + > +class jsonrpc_server : public ::jsonrpc::server > +{ > + public: > + jsonrpc_server (bool verbose, ::lsp::server &inner) > + : server (verbose), m_inner (inner) {} > + > + json::value * > + dispatch (const char *method, const json::value *params, > + const json::value *id) FINAL OVERRIDE; > + > + private: > + json::value * > + do_initialize (const json::value *id, const json::value *params); > + > + json::value * > + do_text_document_did_open (const json::value *params); > + > + json::value * > + do_text_document_did_change (const json::value *params); > + > + json::value * > + do_text_document_definition (const json::value *params); > + > + private: > + ::lsp::server &m_inner; > +}; > + > +} // namespace lsp > + > +#endif /* GCC_LSP_H */ > diff --git a/gcc/selftest-run-tests.c b/gcc/selftest-run-tests.c > index 35ab965..5209aa8 100644 > --- a/gcc/selftest-run-tests.c > +++ b/gcc/selftest-run-tests.c > @@ -70,6 +70,7 @@ selftest::run_tests () > json_c_tests (); > http_server_c_tests (); > json_rpc_c_tests (); > + lsp_c_tests (); > > /* Mid-level data structures. */ > input_c_tests (); > diff --git a/gcc/selftest.h b/gcc/selftest.h > index 2312fb2..1655125 100644 > --- a/gcc/selftest.h > +++ b/gcc/selftest.h > @@ -187,6 +187,7 @@ extern void http_server_c_tests (); > extern void input_c_tests (); > extern void json_c_tests (); > extern void json_rpc_c_tests (); > +extern void lsp_c_tests (); > extern void pretty_print_c_tests (); > extern void read_rtl_function_c_tests (); > extern void rtl_tests_c_tests (); > -- > 1.8.5.3 >