From 07570f990a4b9e1d27f27a8c4996e5ba1f7679c2 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Fri, 19 May 2017 16:57:47 -0400
Subject: [PATCH 18/31] FIXME: LSP parsing

---
 gcc/lsp-main.c                   |  21 ++++---
 gcc/lsp.c                        | 122 +++++++++++++++++++++++++++++++++------
 gcc/lsp.h                        |   8 ++-
 gcc/testsuite/gcc.dg/lsp/test.py |  45 ++++-----------
 4 files changed, 133 insertions(+), 63 deletions(-)

diff --git a/gcc/lsp-main.c b/gcc/lsp-main.c
index a0d358f..7866307 100644
--- a/gcc/lsp-main.c
+++ b/gcc/lsp-main.c
@@ -27,21 +27,24 @@ along with GCC; see the file COPYING3.  If not see
 
 using namespace lsp;
 
-/* FIXME.  */
+/* Implementation of lsp::server.
+   Ideally we'd wire this up to GCC's IR.  */
 
-class test_lsp_server : public lsp::noop_server
+class gcc_lsp_server : public lsp::noop_server
 {
+  /* Implementation of "textDocument/definition":
+     find the definition of the symbol at a given source location.  */
+
   void
   do_text_document_definition (const TextDocumentPositionParams &p,
 			       vec<Location> &out) OVERRIDE
   {
-    // FIXME: currently a hack
+    /* TODO.  */
+    /* FIXME: currently, all we do is echo back the input location.  */
     Location result;
-    result.uri = "test.c";
-    result.range.start.line = 20;
-    result.range.start.character = 10;
-    result.range.end.line = 20;
-    result.range.end.character = 15;
+    result.uri = p.textDocument.uri;
+    result.range.start = p.position;
+    result.range.end = p.position;
     out.safe_push (result);
   }
 };
@@ -52,7 +55,7 @@ void
 serve_lsp (int port)
 {
   // FIXME
-  test_lsp_server lsp_server;
+  gcc_lsp_server lsp_server;
   lsp::jsonrpc_server json_lsp_server (true, lsp_server);
   jsonrpc::http_server http_server (json_lsp_server);
 
diff --git a/gcc/lsp.c b/gcc/lsp.c
index ff25d00..f64643e 100644
--- a/gcc/lsp.c
+++ b/gcc/lsp.c
@@ -32,22 +32,105 @@ using namespace lsp;
 // TODO: autogenerate the interface binding/marshalling/demarshalling code
 // from an interface description.
 
-TextDocumentItem
-TextDocumentItem::from_json (const json::value *params,
-				     json::value *&out_err)
+#define GET_AS_OBJECT_OBJ(PARAMS) \
+  const json::object *obj = (PARAMS)->as_object (); \
+  if (!obj)					    \
+    {						    \
+      out_err = make_invalid_params (NULL, "not an object");   \
+      return result;				    \
+    }
+
+static bool
+extract_int (const json::object *obj, const char *name,
+	     int &out, json::value *&out_err)
 {
-  TextDocumentItem result;
-  const json::object *obj = params->as_object ();
-  if (!obj)
+  const json::value *v = obj->get (name);
+  if (!v)
+    {
+      char *msg = xasprintf ("missing int attribute: \"%s\"", name);
+      out_err = make_invalid_params (NULL, msg);
+      free (msg);
+      return false;
+    }
+  const json::number *n = v->as_number ();
+  if (!n)
+    {
+      char *msg = xasprintf ("not a number: \"%s\"", name);
+      out_err = make_invalid_params (NULL, msg);
+      free (msg);
+      return false;
+    }
+  out = n->get ();
+  return true;
+}
+
+static bool
+extract_string (const json::object *obj, const char *name,
+		const char *&out, json::value *&out_err)
+{
+  const json::value *v = obj->get (name);
+  if (!v)
     {
-      out_err = make_invalid_params (NULL, NULL);
-      return result;
+      char *msg = xasprintf ("missing string attribute: \"%s\"", name);
+      out_err = make_invalid_params (NULL, msg);
+      free (msg);
+      return false;
     }
+  const json::string *s = v->as_string ();
+  if (!s)
+    {
+      char *msg = xasprintf ("not a string: \"%s\"", name);
+      out_err = make_invalid_params (NULL, msg);
+      free (msg);
+      return false;
+    }
+  out = s->get_string ();
+  return true;
+}
+
+#define SET_NUMBER(LHS, NAME)				\
+  do {								\
+    if (!extract_int (obj, NAME, LHS, out_err))		\
+      return result;						\
+  } while (0)
+
+#define SET_STRING(LHS, NAME)				\
+  do {								\
+    if (!extract_string (obj, NAME, LHS, out_err))		\
+      return result;						\
+  } while (0)
+
+Position
+Position::from_json (const json::value *params,
+		     json::value *&out_err)
+{
+  Position result;
+  GET_AS_OBJECT_OBJ (params);
+  SET_NUMBER (result.line, "line");
+  SET_NUMBER (result.character, "character");
+  return result;
+}
+
+TextDocumentIdentifier
+TextDocumentIdentifier::from_json (const json::value *params,
+				   json::value *&out_err)
+{
+  TextDocumentIdentifier result;
+  GET_AS_OBJECT_OBJ (params);
+  SET_STRING (result.uri, "uri");
+  return result;
+}
 
-  result.uri = obj->get ("uri")->as_string ()->get_string ();
-  result.languageId = obj->get ("languageId")->as_string ()->get_string ();
-  result.version = obj->get ("version")->as_number ()->get ();
-  result.text = obj->get ("text")->as_string ()->get_string ();
+TextDocumentItem
+TextDocumentItem::from_json (const json::value *params,
+			     json::value *&out_err)
+{
+  TextDocumentItem result;
+  GET_AS_OBJECT_OBJ (params);
+  SET_STRING (result.uri, "uri");
+  SET_STRING (result.languageId, "languageId");
+  SET_NUMBER (result.version, "version");
+  SET_STRING (result.text, "text");
   return result;
 }
 
@@ -56,12 +139,7 @@ DidOpenTextDocumentParams::from_json (const json::value *params,
 				      json::value *&out_err)
 {
   DidOpenTextDocumentParams result;
-  const json::object *obj = params->as_object ();
-  if (!obj)
-    {
-      out_err = make_invalid_params (NULL, NULL);
-      return result;
-    }
+  GET_AS_OBJECT_OBJ (params);
   // FIXME: error-handling
   result.textDocument
     = TextDocumentItem::from_json (obj->get ("textDocument"), out_err);
@@ -73,6 +151,8 @@ DidChangeTextDocumentParams::from_json (const json::value *params,
 					json::value *&out_err)
 {
   DidChangeTextDocumentParams result;
+  GET_AS_OBJECT_OBJ (params);
+
   // FIXME
   return result;
 }
@@ -82,7 +162,11 @@ TextDocumentPositionParams::from_json (const json::value *params,
 				       json::value *&out_err)
 {
   TextDocumentPositionParams result;
-  // FIXME
+  GET_AS_OBJECT_OBJ (params);
+  result.textDocument
+    = TextDocumentIdentifier::from_json (obj->get ("textDocument"), out_err);
+  result.position
+    = Position::from_json (obj->get ("position"), out_err);
   return result;
 }
 
diff --git a/gcc/lsp.h b/gcc/lsp.h
index f44e278..b9c99c8 100644
--- a/gcc/lsp.h
+++ b/gcc/lsp.h
@@ -28,6 +28,8 @@ typedef const char *DocumentUri;
 
 struct Position
 {
+  static Position from_json (const json::value *params,
+			     json::value *&out_err);
   json::value *to_json () const;
 
   int line;
@@ -57,6 +59,9 @@ struct Location
 
 struct TextDocumentIdentifier
 {
+  static TextDocumentIdentifier from_json (const json::value *params,
+					   json::value *&out_err);
+
   DocumentUri uri;
 };
 
@@ -67,7 +72,7 @@ struct TextDocumentItem
 
   DocumentUri uri;
   const char *languageId;
-  double version;
+  int version;
   const char *text;
 };
 
@@ -178,4 +183,3 @@ class jsonrpc_server : public ::jsonrpc::server
 } // namespace lsp
 
 #endif  /* GCC_LSP_H  */
-
diff --git a/gcc/testsuite/gcc.dg/lsp/test.py b/gcc/testsuite/gcc.dg/lsp/test.py
index a793745..b08d119 100644
--- a/gcc/testsuite/gcc.dg/lsp/test.py
+++ b/gcc/testsuite/gcc.dg/lsp/test.py
@@ -1,18 +1,17 @@
-import jsonrpc # pip install json-rpc
-
-def test():
-    print('test')
+import json
 
+import jsonrpc # pip install json-rpc
 import requests
-import json
+
+# Various types to model the LSP interface
 
 class Position:
-    def __init__(self, line, column):
+    def __init__(self, line, character):
         self.line = line
-        self.column = column
+        self.character = character
 
     def to_json(self):
-        return {'line': self.line, 'column': self.column}
+        return {'line': self.line, 'character': self.character}
 
 class TextDocumentIdentifier:
     def __init__(self, uri):
@@ -26,6 +25,7 @@ class TextDocumentPositionParams:
         self.textDocument = textDocument
         self.position = position
 
+# A wrapper for making LSP calls against a server
 
 class LspProxy:
     def __init__(self, url):
@@ -43,8 +43,8 @@ class LspProxy:
     def post_request(self, method, params):
         payload = self.make_request(method, params)
         headers = {'content-type': 'application/json'}
-        response = requests.post(
-            self.url, data=json.dumps(payload), headers=headers)
+        response = requests.post(self.url, data=json.dumps(payload),
+                                 headers=headers)
         print('response: %r' % response)
         print('response.json(): %r' % response.json())
         return response.json()
@@ -54,33 +54,12 @@ class LspProxy:
                                       {"textDocument" : textDocument.to_json(),
                                        "position" : position.to_json()})
         print(json_resp)
-        
 
 def main():
     url = "http://localhost:4000/jsonrpc"
-    headers = {'content-type': 'application/json'}
-
-    """
-    # Example echo method
-    payload = {
-        "method": "echo",
-        "params": ["echome!"],
-        "jsonrpc": "2.0",
-        "id": 0,
-    }
-    response = requests.post(
-        url, data=json.dumps(payload), headers=headers)
-    print('response: %r' % response)
-    response = response.json()
-    print('response.json(): %r' % response)
-
-    assert response["result"] == "echome!"
-    assert response["jsonrpc"]
-    assert response["id"] == 0
-    """
-
     proxy = LspProxy(url)
-    print(proxy.goto_definition(TextDocumentIdentifier('test.c'), Position(9, 17)))
+    print(proxy.goto_definition(TextDocumentIdentifier('test.c'),
+                                Position(9, 17)))
 
 if __name__ == "__main__":
     main()
-- 
1.8.5.3

