From 4296ca5088cffafae506522d6c1186fe011220d6 Mon Sep 17 00:00:00 2001 From: David Malcolm Date: Fri, 19 May 2017 14:10:16 -0400 Subject: [PATCH 16/31] FIXME: wire up the LSP server to the port --- gcc/http-server.c | 2 +- gcc/http-server.h | 4 +- gcc/json-rpc.c | 46 +++++- gcc/json-rpc.h | 19 +++ gcc/lsp-main.c | 99 +++---------- gcc/lsp-main.h | 25 ++++ gcc/lsp.c | 312 +++++++++++++++++++-------------------- gcc/lsp.h | 157 +++++++++++++++++++- gcc/testsuite/gcc.dg/lsp/test.py | 55 ++++++- gcc/toplev.c | 2 +- 10 files changed, 479 insertions(+), 242 deletions(-) create mode 100644 gcc/lsp-main.h diff --git a/gcc/http-server.c b/gcc/http-server.c index ba5fc13..50b0c34 100644 --- a/gcc/http-server.c +++ b/gcc/http-server.c @@ -141,7 +141,7 @@ http::server::on_read (file_descriptor fd, size_t length, const char *buf) request req; req_start += req.parse_buffer (length, buf); http::response resp; - on_request (req, resp); + on_http_request (req, resp); char *resp_str = resp.to_str (); if (1) inform (UNKNOWN_LOCATION, "sending http response: %qs", diff --git a/gcc/http-server.h b/gcc/http-server.h index 8a28ce8..48955cb 100644 --- a/gcc/http-server.h +++ b/gcc/http-server.h @@ -74,8 +74,8 @@ class server : public ::server void on_read (file_descriptor fd, size_t length, const char *buf) OVERRIDE FINAL; - virtual void on_request (const http::request &request, - http::response &response) = 0; + virtual void on_http_request (const http::request &request, + http::response &response) = 0; private: bool m_verbose; diff --git a/gcc/json-rpc.c b/gcc/json-rpc.c index a9bafbd..d81ee51 100644 --- a/gcc/json-rpc.c +++ b/gcc/json-rpc.c @@ -21,6 +21,7 @@ along with GCC; see the file COPYING3. If not see #include "system.h" #include "coretypes.h" #include "json.h" +#include "http-server.h" #include "json-rpc.h" #include "selftest.h" @@ -167,6 +168,30 @@ jsonrpc::server::handle_request_string (const char *utf8) return handle_request (strlen (utf8), utf8); } +/* class ::jsonrpc::http_server : public ::http::server. */ + +jsonrpc::http_server::http_server (jsonrpc::server &json_handler) +: ::http::server (true), m_json_handler (json_handler) +{} + +/* Handle HTTP requests by parsing the content as JSON-RPC, and delegating + them to a ::jsonrpc::server. */ + +void +jsonrpc::http_server::on_http_request (const http::request &http_request, + http::response &http_response) +{ + size_t length = http_request.get_content_length (); + const char *buf = http_request.get_content (); + if (0) + fprintf (stderr, "got content: '%.*s'\n", (int)length, buf); + json::value *json_response = m_json_handler.handle_request (length, buf); + char *response_str = json_response->to_str (); + delete json_response; + http_response.set_content (strlen (response_str), response_str); + free (response_str); +} + #if CHECKING_P namespace selftest { @@ -362,6 +387,25 @@ test_malformed_json () delete response; } +static void +test_http_server () +{ + http::request req; + const char *in = ("POST /jsonrpc HTTP/1.1\r\n" + "Content-Length: FIXME\r\n" + "\r\n" + "{\"jsonrpc\": \"2.0\", \"method\": \"subtract\"," + " \"params\": [42, 23], \"id\": 1}"); + req.parse_buffer (strlen (in), in); + + test_server s (false); + http_server http_server (s); + http::response resp; + http_server.on_http_request (req, resp); + ASSERT_STREQ ("{\"jsonrpc\": \"2.0\", \"id\": 1, \"result\": 19}", + resp.get_content ()); // FIXME: length? +} + /* Run all of the selftests within this file. */ void @@ -373,7 +417,7 @@ json_rpc_c_tests () test_method_not_a_string (); test_method_not_found (); test_malformed_json (); - // TODO: bogus JSON + test_http_server (); } } // namespace selftest diff --git a/gcc/json-rpc.h b/gcc/json-rpc.h index 4b5fd4b..ef9ad2d 100644 --- a/gcc/json-rpc.h +++ b/gcc/json-rpc.h @@ -28,6 +28,7 @@ class server public: server (bool verbose) : m_verbose (verbose) {} virtual ~server () {} + json::value *handle_request (json::value *); json::value *handle_request (size_t sz, const char *buf); json::value *handle_request_string (const char *utf8); @@ -64,6 +65,24 @@ extern json::value *make_invalid_params (const json::value *id, extern json::value *make_success (const json::value *id, json::value *result); + +/* A subclass of ::http::server that handles HTTP requests + by parsing the content as JSON-RPC, and delegating them to + a ::jsonrpc::server. */ + +class http_server : public ::http::server +{ + public: + http_server (::jsonrpc::server &json_handler); + + void on_http_request (const http::request &http_request, + http::response &http_response) OVERRIDE FINAL; + + private: + ::jsonrpc::server &m_json_handler; +}; + + } // namespace jsonrpc #endif /* GCC_JSON_RPC_H */ diff --git a/gcc/lsp-main.c b/gcc/lsp-main.c index 5b76d3c..a0d358f 100644 --- a/gcc/lsp-main.c +++ b/gcc/lsp-main.c @@ -23,96 +23,39 @@ along with GCC; see the file COPYING3. If not see #include "http-server.h" #include "json.h" #include "json-rpc.h" -#include "selftest.h" +#include "lsp.h" -class jsonrpc_server : public http::server -{ - public: - jsonrpc_server (jsonrpc::server &json_handler) - : http::server (true), m_json_handler (json_handler) {} - - void on_request (const http::request &http_request, - http::response &http_response) OVERRIDE FINAL - { - size_t length = http_request.get_content_length (); - const char *buf = http_request.get_content (); - if (1) - fprintf (stderr, "got content: '%.*s'\n", (int)length, buf); - json::value *json_response = m_json_handler.handle_request (length, buf); - char *response_str = json_response->to_str (); - http_response.set_content (strlen (response_str), response_str); - free (response_str); - } - - private: - jsonrpc::server &m_json_handler; -}; - -// FIXME: copy of test server... - -using namespace jsonrpc; +using namespace lsp; -namespace { +/* FIXME. */ -class test_server : public jsonrpc::server +class test_lsp_server : public lsp::noop_server { - public: - test_server (bool verbose) : server (verbose) {} - - json::value * - dispatch (const char *method, const json::value *params, - const json::value *id) FINAL OVERRIDE - { - if (0 == strcmp (method, "echo")) - return do_echo (params, id); - if (0 == strcmp (method, "subtract")) - return do_subtract (params, id); - return make_method_not_found (id, method); - } - - json::value * - do_echo (const json::value *params, const json::value *id) - { - const json::array *positional = params->as_array (); - if (!positional) - return make_invalid_params (id, "params was not an array"); - if (positional->get_length () != 1) - return make_invalid_params (id, "length of params was not 1"); - const json::value *arg0 = positional->get (0); - return make_success (id, arg0->clone ()); - } - - json::value * - do_subtract (const json::value *params, const json::value *id) + void + do_text_document_definition (const TextDocumentPositionParams &p, + vec &out) OVERRIDE { - const json::array *positional = params->as_array (); - if (!positional) - return make_invalid_params (id, "params was not an array"); - if (positional->get_length () != 2) - return make_invalid_params (id, "length of params was not 2"); - - const json::number *arg0 = positional->get (0)->as_number (); - if (!arg0) - return make_invalid_params (id, "param 0 was not a number"); - const json::number *arg1 = positional->get (1)->as_number (); - if (!arg1) - return make_invalid_params (id, "param 1 was not a number"); - - double result = arg0->get () - arg1->get (); - - return make_success (id, new json::number (result)); + // FIXME: currently a hack + 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; + out.safe_push (result); } }; -} // anonymous namespace - /* Serve LSP on PORT. */ void serve_lsp (int port) { - test_server ts (true); - jsonrpc_server s (ts); + // FIXME + test_lsp_server lsp_server; + lsp::jsonrpc_server json_lsp_server (true, lsp_server); + jsonrpc::http_server http_server (json_lsp_server); + fprintf (stderr, "serving on port %i\n", port); - s.serve (port); + http_server.serve (port); } diff --git a/gcc/lsp-main.h b/gcc/lsp-main.h new file mode 100644 index 0000000..ec57201 --- /dev/null +++ b/gcc/lsp-main.h @@ -0,0 +1,25 @@ +/* Entrypoint to 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 +. */ + +#ifndef GCC_LSP_MAIN_H +#define GCC_LSP_MAIN_H + +extern void serve_lsp (int port); + +#endif /* GCC_LSP_MAIN_H */ diff --git a/gcc/lsp.c b/gcc/lsp.c index 682efcf..ff25d00 100644 --- a/gcc/lsp.c +++ b/gcc/lsp.c @@ -21,181 +21,179 @@ along with GCC; see the file COPYING3. If not see #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; -typedef const char *DocumentUri; - +// TODO: autogenerate the interface binding/marshalling/demarshalling code +// from an interface description. -/* Interfaces from the protocol specification (which uses camel case). */ +TextDocumentItem +TextDocumentItem::from_json (const json::value *params, + json::value *&out_err) +{ + TextDocumentItem result; + const json::object *obj = params->as_object (); + if (!obj) + { + out_err = make_invalid_params (NULL, NULL); + 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 (); + return result; +} -// Exceptions would be nicer than passing around the out_err +DidOpenTextDocumentParams +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; + } + // FIXME: error-handling + result.textDocument + = TextDocumentItem::from_json (obj->get ("textDocument"), out_err); + return result; +} -// TODO: autogenerate the interface binding/marshalling/demarshalling code -// from an interface description. +DidChangeTextDocumentParams +DidChangeTextDocumentParams::from_json (const json::value *params, + json::value *&out_err) +{ + DidChangeTextDocumentParams result; + // FIXME + return result; +} -struct TextDocumentItem +TextDocumentPositionParams +TextDocumentPositionParams::from_json (const json::value *params, + json::value *&out_err) { - static TextDocumentItem from_json (const json::value *params, - json::value *&out_err) - { - TextDocumentItem result; - const json::object *obj = params->as_object (); - if (!obj) - { - out_err = make_invalid_params (NULL, NULL); - 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 (); - return result; - } - - DocumentUri uri; - const char *languageId; - double version; - const char *text; -}; - -struct DidOpenTextDocumentParams + TextDocumentPositionParams result; + // FIXME + return result; +} + +/* struct Position. */ + +json::value * +Position::to_json () const { - static 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; - } - // FIXME: error-handling - result.textDocument - = TextDocumentItem::from_json (obj->get ("textDocument"), out_err); - return result; - } - - TextDocumentItem textDocument; -}; - -struct DidChangeTextDocumentParams + 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 { - public: - static DidChangeTextDocumentParams from_json (const json::value *params, - json::value *&out_err) - { - DidChangeTextDocumentParams result; - // FIXME - return result; - } - - private: -#if 0 - VersionedTextDocumentIdentifier textDocument; - auto_vec contentChanges; -#endif -}; - -/* An abstract base class for implementing the LSP as vfunc calls. */ - -class lsp_server + 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 { - public: - virtual ~lsp_server () {} + json::object *result = new json::object (); + result->set ("uri", new json::string (uri)); + result->set ("range", range.to_json ()); + return result; +} - virtual void - do_text_document_did_open (const DidOpenTextDocumentParams &p) = 0; +/* class lsp::jsonrpc_server : public ::jsonrpc::server. */ - virtual void - do_text_document_did_change (const DidChangeTextDocumentParams &p) = 0; -}; +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); +} -/* A concrete subclass of lsp_server that implements everything as a no-op. */ +json::value * +lsp::jsonrpc_server::do_initialize (const json::value *id, + const json::value *params) +{ + // FIXME: for now, ignore params -class noop_lsp_server : public lsp_server + json::object *server_caps = new json::object (); + json::object *result = new json::object (); + result->set ("capabilities", server_caps); + return make_success (id, result); +} + +json::value * +lsp::jsonrpc_server::do_text_document_did_open (const json::value *params) { - void - do_text_document_did_open (const DidOpenTextDocumentParams &p) OVERRIDE - { - // no-op - } - - void - do_text_document_did_change (const DidChangeTextDocumentParams &p) OVERRIDE - { - // no-op - } -}; - -/* A jsonrpc::server subclass that decodes incoming JSON-RPC requests - and dispatches them to an lsp_server instance as vfunc calls. */ - -class json_lsp_server : public jsonrpc::server + json::value *err = NULL; + DidOpenTextDocumentParams p + = DidOpenTextDocumentParams::from_json (params, err); + if (err) + return 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) { - public: - json_lsp_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 - { - 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); - return make_method_not_found (id, method); - } - - json::value * - 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); - } - - json::value * - do_text_document_did_open (const json::value *params) - { - json::value *err = NULL; - DidOpenTextDocumentParams p - = DidOpenTextDocumentParams::from_json (params, err); - if (err) - return 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 * - do_text_document_did_change (const json::value *params) - { - json::value *err = NULL; - DidChangeTextDocumentParams p - = DidChangeTextDocumentParams::from_json (params, err); - if (err) - return 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 - } - - private: - lsp_server &m_inner; -}; + json::value *err = NULL; + DidChangeTextDocumentParams p + = DidChangeTextDocumentParams::from_json (params, err); + if (err) + return 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) +{ + json::value *err = NULL; + TextDocumentPositionParams p + = TextDocumentPositionParams::from_json (params, err); + if (err) + return err; + + auto_vec 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 @@ -206,8 +204,8 @@ namespace selftest { static void test_simple () { - noop_lsp_server noop; - json_lsp_server js (true, noop); + noop_server noop; + lsp::jsonrpc_server js (true, noop); const char *init_request = ("{\"jsonrpc\": \"2.0\", \"method\": \"initialize\"," " \"params\": [42, 23], \"id\": 1}"); // FIXME diff --git a/gcc/lsp.h b/gcc/lsp.h index 6b04025..f44e278 100644 --- a/gcc/lsp.h +++ b/gcc/lsp.h @@ -20,7 +20,162 @@ along with GCC; see the file COPYING3. If not see #ifndef GCC_LSP_H #define GCC_LSP_H -extern void serve_lsp (int port); +namespace lsp { + +typedef const char *DocumentUri; + +/* Interfaces from the protocol specification (which uses camel case). */ + +struct Position +{ + 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 +{ + DocumentUri uri; +}; + +struct TextDocumentItem +{ + static TextDocumentItem from_json (const json::value *params, + json::value *&out_err); + + DocumentUri uri; + const char *languageId; + double version; + const char *text; +}; + +struct DidOpenTextDocumentParams +{ + static DidOpenTextDocumentParams from_json (const json::value *params, + json::value *&out_err); + + TextDocumentItem textDocument; +}; + +struct DidChangeTextDocumentParams +{ + public: + static DidChangeTextDocumentParams from_json (const json::value *params, + json::value *&out_err); + + private: +#if 0 + VersionedTextDocumentIdentifier textDocument; + auto_vec contentChanges; +#endif +}; + +struct TextDocumentPositionParams +{ + static TextDocumentPositionParams from_json (const json::value *params, + json::value *&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 &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 &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/testsuite/gcc.dg/lsp/test.py b/gcc/testsuite/gcc.dg/lsp/test.py index 5acae6b..a793745 100644 --- a/gcc/testsuite/gcc.dg/lsp/test.py +++ b/gcc/testsuite/gcc.dg/lsp/test.py @@ -6,11 +6,61 @@ def test(): import requests import json +class Position: + def __init__(self, line, column): + self.line = line + self.column = column + + def to_json(self): + return {'line': self.line, 'column': self.column} + +class TextDocumentIdentifier: + def __init__(self, uri): + self.uri = uri + + def to_json(self): + return {'uri': self.uri} + +class TextDocumentPositionParams: + def __init__(self, textDocument, position): + self.textDocument = textDocument + self.position = position + + +class LspProxy: + def __init__(self, url): + self.url = url + self.next_id = 0 + + def make_request(self, method, params): + json_req = {"method": method, + "params": params, + "jsonrpc": "2.0", + "id": self.next_id} + self.next_id += 1 + return json_req + + 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) + print('response: %r' % response) + print('response.json(): %r' % response.json()) + return response.json() + + def goto_definition(self, textDocument, position): + json_resp = self.post_request('textDocument/definition', + {"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", @@ -27,7 +77,10 @@ def main(): assert response["result"] == "echome!" assert response["jsonrpc"] assert response["id"] == 0 + """ + + proxy = LspProxy(url) + print(proxy.goto_definition(TextDocumentIdentifier('test.c'), Position(9, 17))) if __name__ == "__main__": main() - diff --git a/gcc/toplev.c b/gcc/toplev.c index 681bda8..9532106 100644 --- a/gcc/toplev.c +++ b/gcc/toplev.c @@ -79,7 +79,7 @@ along with GCC; see the file COPYING3. If not see #include "omp-offload.h" #include "hsa-common.h" #include "edit-context.h" -#include "lsp.h" +#include "lsp-main.h" #if defined(DBX_DEBUGGING_INFO) || defined(XCOFF_DEBUGGING_INFO) #include "dbxout.h" -- 1.8.5.3