diff mbox

[15/17] Language Server Protocol: add lsp::server abstract base class

Message ID 1500926714-56988-16-git-send-email-dmalcolm@redhat.com
State New
Headers show

Commit Message

David Malcolm July 24, 2017, 8:05 p.m. UTC
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.
	* 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

Comments

Trevor Saunders July 27, 2017, 4:14 a.m. UTC | #1
On Mon, Jul 24, 2017 at 04:05:12PM -0400, David Malcolm 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.

I think my big question on this part of the series is why you chose to
implement this yourself instead of using something like libjsonrpccpp
for most of it.  I don't have any attachment to that particular library,
but I don't see any real advantage to be gained from implementing a json
parser ourselves.

> 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.

That's not my favorite approach to this space, but its certainly a
common and reasonable one.   You can probably get rid of the virtual
call overhead with final, and in networking or at least file descriptor
based communication it almost certainly doesn't matter anyway.

Trev
Richard Biener July 27, 2017, 7:55 a.m. UTC | #2
On Mon, Jul 24, 2017 at 10:05 PM, David Malcolm <dmalcolm@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
>
David Malcolm July 28, 2017, 2:48 p.m. UTC | #3
On Thu, 2017-07-27 at 00:14 -0400, Trevor Saunders wrote:
> On Mon, Jul 24, 2017 at 04:05:12PM -0400, David Malcolm 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.
> 
> I think my big question on this part of the series is why you chose
> to
> implement this yourself instead of using something like libjsonrpccpp
> for most of it.  I don't have any attachment to that particular
> library,
> but I don't see any real advantage to be gained from implementing a
> json
> parser ourselves.

I think my thought was to reduce dependencies, but, yes, that looks
like a much better approach, thanks.

> > 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.
> 
> That's not my favorite approach to this space, but its certainly a
> common and reasonable one.   You can probably get rid of the virtual
> call overhead with final, and in networking or at least file
> descriptor
> based communication it almost certainly doesn't matter anyway.
> 
> Trev
>
David Malcolm July 28, 2017, 4:02 p.m. UTC | #4
On Thu, 2017-07-27 at 09:55 +0200, Richard Biener wrote:
> On Mon, Jul 24, 2017 at 10:05 PM, David Malcolm <dmalcolm@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.
> 

Thanks; will do for next iteration of the kit.
diff mbox

Patch

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 ();