[PATCH] D29451: Add a prototype for clangd v0.1

2017-02-03 Thread Jusufadis Bakamovic via Phabricator via cfe-commits
Adi added a comment.

I had some spare time ... I hope you don't mind the comments. Hopefully you 
will find something useful :)




Comment at: clangd/ClangDMain.cpp:65-70
+std::vector JSON;
+JSON.resize(Len);
+std::cin.read(JSON.data(), Len);
+
+// Insert a trailing null byte. YAML parser requires this.
+JSON.push_back('\0');

Avoid unnecessary JSON.resize(Len) & potential reallocation during 
JSON.push_back('\0') by allocating enough space in advance: std::vector 
JSON(Len + 1);



Comment at: clangd/ClangDMain.cpp:73-77
+  // Log the message.
+  Logs << "<-- ";
+  Logs.write(JSON.data(), JSON.size());
+  Logs << '\n';
+  Logs.flush();

Since llvm::StringRef does not own the data, I think it would make more sense 
to do the logging after Dispatcher.call().



Comment at: clangd/ClangDMain.cpp:80
+  // Finally, execute the action for this JSON message.
+  Dispatcher.call(llvm::StringRef(JSON.data(), JSON.size() - 1));
+}

You are building llvm::StringRef without the trailing null byte here, yet 
comment above states that YAML parser requires a null byte.



Comment at: clangd/ClangDMain.cpp:80
+  // Finally, execute the action for this JSON message.
+  Dispatcher.call(llvm::StringRef(JSON.data(), JSON.size() - 1));
+}

Adi wrote:
> You are building llvm::StringRef without the trailing null byte here, yet 
> comment above states that YAML parser requires a null byte.
Perhaps make a log entry if Dispatcher.call() failed.



Comment at: clangd/JSONRPCDispatcher.cpp:32-40
+  Logs << "Method ignored.\n";
+  // Return that this method is unsupported.
+  writeMessage(
+  R"({"jsonrpc":"2.0","id":)" + ID +
+  R"(,"error":{"code":-32601}})");
+}
+

I would extract this implementation to the separate handler class (e.g. 
OperationNotSupportedHandler) and make this an interface class. It would make 
code and intent more explicit.



Comment at: clangd/JSONRPCDispatcher.cpp:82-83
+  // This should be "2.0". Always.
+  Version = dyn_cast(Value);
+  if (Version->getRawValue() != "2.0")
+return false;

dyn_cast might fail and leave you with Version set to nullptr. Using 
static_cast is better approach if you are sure the cast will be successful.



Comment at: clangd/JSONRPCDispatcher.cpp:112-120
+  if (!Method)
+return false;
+  llvm::SmallString<10> MethodStorage;
+  auto I = Handlers.find(Method->getValue(MethodStorage));
+  auto &Handler = I != Handlers.end() ? I->second : UnknownHandler;
+  if (Id)
+Handler->handleMethod(nullptr, Id->getRawValue());

Perhaps refactor this code and the one above into the separate method to gain 
some more readability.



Comment at: clangd/JSONRPCDispatcher.h:25-32
+  virtual ~Handler() = default;
+
+  /// Called when the server receives a method call. This is supposed to return
+  /// a result on Outs.
+  virtual void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID);
+  /// Called when the server receives a notification. No result should be
+  /// written to Outs.

To avoid virtual dispatch one may rewrite this class in terms of CRTP. E.g.

```
template 
class Handler {
public:
Handler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
  : Outs(Outs), Logs(Logs) {}
virtual ~Handler() = default;

void handleMethod(const llvm::yaml::MappingMode *Params, StringRef ID) { 
static_cast(this)->handleMethod(Params, ID); 
}
void handleNotification(const llvm::yaml::MappingMode *Params) { 
static_cast(this)->handleNotification(Params); 
}

protected:
llvm::raw_ostream &Outs;
llvm::raw_ostream &Logs;

void writeMessage(const Twine &Message);
};
```
And then use it as:

```
struct MyConcreteHandler : public Handler {
MyConcreteHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs /* other 
params if necessary */) 
  : Handler(Outs, Logs) /* init other members */
{}
 
void handleMethod(const llvm::yaml::MappingMode *Params, StringRef ID) { 
// impl
}
void handleNotification(const llvm::yaml::MappingMode *Params) { 
// impl 
}
};
```



Comment at: clangd/JSONRPCDispatcher.h:29
+  /// a result on Outs.
+  virtual void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID);
+  /// Called when the server receives a notification. No result should be

const ptr/ref to Params?



Comment at: clangd/JSONRPCDispatcher.h:32
+  /// written to Outs.
+  virtual void handleNotification(llvm::yaml::MappingNode *Params);
+

const ptr/ref to Params?



Comment at: clangd/Protocol.cpp:11
+// This file contains the serialization code for the LSP structs.
+// FIXME

[PATCH] D29451: Add a prototype for clangd v0.1

2017-02-03 Thread Jusufadis Bakamovic via Phabricator via cfe-commits
Adi added inline comments.



Comment at: clangd/JSONRPCDispatcher.h:25-32
+  virtual ~Handler() = default;
+
+  /// Called when the server receives a method call. This is supposed to return
+  /// a result on Outs.
+  virtual void handleMethod(llvm::yaml::MappingNode *Params, StringRef ID);
+  /// Called when the server receives a notification. No result should be
+  /// written to Outs.

klimek wrote:
> Adi wrote:
> > To avoid virtual dispatch one may rewrite this class in terms of CRTP. E.g.
> > 
> > ```
> > template 
> > class Handler {
> > public:
> > Handler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs)
> >   : Outs(Outs), Logs(Logs) {}
> > virtual ~Handler() = default;
> > 
> > void handleMethod(const llvm::yaml::MappingMode *Params, StringRef ID) 
> > { 
> > static_cast(this)->handleMethod(Params, ID); 
> > }
> > void handleNotification(const llvm::yaml::MappingMode *Params) { 
> > static_cast(this)->handleNotification(Params); 
> > }
> > 
> > protected:
> > llvm::raw_ostream &Outs;
> > llvm::raw_ostream &Logs;
> > 
> > void writeMessage(const Twine &Message);
> > };
> > ```
> > And then use it as:
> > 
> > ```
> > struct MyConcreteHandler : public Handler {
> > MyConcreteHandler(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs /* 
> > other params if necessary */) 
> >   : Handler(Outs, Logs) /* init other members */
> > {}
> >  
> > void handleMethod(const llvm::yaml::MappingMode *Params, StringRef ID) 
> > { 
> > // impl
> > }
> > void handleNotification(const llvm::yaml::MappingMode *Params) { 
> > // impl 
> > }
> > };
> > ```
> > To avoid virtual dispatch one may rewrite this class in terms of CRTP. E.g.
> 
> Why would virtual dispatch be a problem here? It seems strictly simpler this 
> way.
> 
I've mentioned it only as an alternative approach. I was not implying that 
virtual dispatch is a problem. It's a matter of taste in this case really.



Comment at: clangd/Protocol.cpp:11
+// This file contains the serialization code for the LSP structs.
+// FIXME: This is extremely repetetive and ugly. Is there a better way?
+//

Adi wrote:
> To some degree it could be refactored. I would go for CRTP again here. It 
> would simplify the code a little bit. E.g.
> 
> ```
> template 
> struct Params {
> static llvm::Optional parse(const llvm::yaml::MappingNode& node) {
> T Result;
> for (auto& NextKeyValue : node) {
> auto *KeyString = 
> dyn_cast(NextKeyValue.getKey());
> if (!KeyString)
> return llvm::None;
> 
> llvm::SmallString<10> KeyStorage;
> StringRef KeyValue = KeyString->getValue(KeyStorage);
> auto *Value = 
> dyn_cast_or_null(NextKeyValue.getValue());
> if (!Value)
> return llvm::None;
> 
> Result = static_cast(this)->setParam(KeyValue, *Value); // or 
> make Result an argument if worried about RVO
> if (!Result) // we can make this more neat by placing it inside a 
> for loop condition (ranged-for doesn't allow this)
> return llvm::none;
> }
> return Result;
> }
> };
> 
> struct TextDocumentIdentifier : public Params {
> /// The text document's URI.
> std::string uri;
> 
> llvm::Optional setParam(StringRef KeyValue, const 
> llvm::yaml::ScalarNode& Value) {
> TextDocumentIdentifier Result;
> llvm::SmallString<10> Storage;
> if (KeyValue == "uri") {
> Result.uri = Value.getValue(Storage);
> } else if (KeyValue == "version") {
> // FIXME: parse version, but only for 
> VersionedTextDocumentIdentifiers.
> } else {
> return llvm::None;
> }
> return Result;
> }
> };
> 
> struct Position : public Params {
> /// Line position in a document (zero-based).
> int line;
> 
> /// Character offset on a line in a document (zero-based).
> int character;
> 
> llvm::Optional setParam(StringRef KeyValue, const 
> llvm::yaml::ScalarNode& Value) {
> Position Result;
> llvm::SmallString<10> Storage;
> if (KeyValue == "line") {
> long long Val;
> if (llvm::getAsSignedInteger(Value.getValue(Storage), 0, Val))
> return llvm::None;
> Result.line = Val;
> } else if (KeyValue == "character") {
> long long Val;
> if (llvm::getAsSignedInteger(Value.getValue(Storage), 0, Val))
> return llvm::None;
> Result.character = Val;
> } else {
> return llvm::None;
> }
> return Result;
> }
> };
> ```
> 
> I am not really familiar with all of the dependencies here but I may suggest 
> that one might also try to generalize this approach even further by 
> encapsulating the