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
>

Reply via email to