From 6bb5bb1931badac389fe24b50ab7c78ca705e645 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 23 May 2017 12:56:02 -0400
Subject: [PATCH 23/28] Language Server Protocol: add lsp::server abstract base
 class

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 protocol is specified in camel case, and so these classes use
camel case, 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 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                | 188 ++++++++++++++++++++++++++++++
 gcc/selftest-run-tests.c |   1 +
 gcc/selftest.h           |   1 +
 5 files changed, 482 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..ed5d68e
--- /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..8566b54
--- /dev/null
+++ b/gcc/lsp.h
@@ -0,0 +1,188 @@
+/* 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
+{
+  static Position from_json (const json::value *params,
+			     char *&out_err);
+  json::value *to_json () const;
+
+  int line;
+  int character;
+};
+
+struct Range
+{
+  json::value *to_json () const;
+
+  Position start;
+  Position end;
+};
+
+struct Location
+{
+  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
+{
+  static TextDocumentIdentifier from_json (const json::value *params,
+					   char *&out_err);
+
+  DocumentUri uri;
+};
+
+struct TextDocumentItem
+{
+  static TextDocumentItem from_json (const json::value *params,
+				     char *&out_err);
+
+  DocumentUri uri;
+  const char *languageId;
+  int version;
+  const char *text;
+};
+
+struct DidOpenTextDocumentParams
+{
+  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
+{
+  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 &p) OVERRIDE
+  {
+    // no-op
+  }
+
+  void
+  do_text_document_did_change (const DidChangeTextDocumentParams &p) OVERRIDE
+  {
+    // no-op
+  }
+
+  void
+  do_text_document_definition (const TextDocumentPositionParams &p,
+			       vec<Location> &out) 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 6cbd3d8..b865b07 100644
--- a/gcc/selftest-run-tests.c
+++ b/gcc/selftest-run-tests.c
@@ -69,6 +69,7 @@ selftest::run_tests ()
   http_server_c_tests ();
   json_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 1c2879b..39afb84 100644
--- a/gcc/selftest.h
+++ b/gcc/selftest.h
@@ -186,6 +186,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

