-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[mlir-lsp] Abstract input and output of the JSONTransport
#129320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,14 +43,86 @@ enum JSONStreamStyle { | |
Delimited | ||
}; | ||
|
||
/// An abstract class used by the JSONTransport to read JSON message. | ||
class JSONTransportInput { | ||
public: | ||
explicit JSONTransportInput(JSONStreamStyle style = JSONStreamStyle::Standard) | ||
: style(style) {} | ||
virtual ~JSONTransportInput() = default; | ||
|
||
virtual bool getError() const = 0; | ||
virtual bool isEndOfInput() const = 0; | ||
|
||
/// Read in a message from the input stream. | ||
virtual LogicalResult readMessage(std::string &json) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why virtual here? If we already accept and dispatch on the stream style, I don't see this should be overridable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wanted to give way more freedom to whoever is going to inherit from that class with a possibility to use different stream style. |
||
return style == JSONStreamStyle::Delimited ? readDelimitedMessage(json) | ||
: readStandardMessage(json); | ||
} | ||
virtual LogicalResult readDelimitedMessage(std::string &json) = 0; | ||
virtual LogicalResult readStandardMessage(std::string &json) = 0; | ||
|
||
private: | ||
/// The JSON stream style to use. | ||
JSONStreamStyle style; | ||
}; | ||
|
||
/// An abstract class used by the JSONTransport to write JSON messages. | ||
class JSONTransportOutput { | ||
public: | ||
explicit JSONTransportOutput() = default; | ||
virtual ~JSONTransportOutput() = default; | ||
|
||
virtual void sendMessage(const llvm::json::Value &msg) = 0; | ||
}; | ||
|
||
/// Concrete implementation of the JSONTransportInput that reads from a file. | ||
class JSONTransportInputOverFile : public JSONTransportInput { | ||
public: | ||
explicit JSONTransportInputOverFile( | ||
std::FILE *in, JSONStreamStyle style = JSONStreamStyle::Standard) | ||
: JSONTransportInput(style), in(in) {} | ||
|
||
bool getError() const final { return ferror(in); } | ||
bool isEndOfInput() const final { return feof(in); } | ||
|
||
LogicalResult readDelimitedMessage(std::string &json) final; | ||
LogicalResult readStandardMessage(std::string &json) final; | ||
|
||
private: | ||
std::FILE *in; | ||
}; | ||
|
||
/// Concrete implementation of the JSONTransportOutput that writes to a stream. | ||
class JSONTransportOutputOverStream : public JSONTransportOutput { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit confused why this is being abstracted out. Why can't your custom implementation use raw_ostream? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the main reason was to keep that output class simple and match to what json transport is expecting. User may decide to either implement other functionality such as colors, seek etc or not to implement them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With these kinds of things I'd really prefer to avoid abstracting until we need to. Most of the time it ends up not being necessary. For example, why would you need/want colors for a transport class? You generally aren't observing the data being transferred outside of logging/testing (or shouldn't be at least). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do agree that raw_ostream seems sufficient even for extreme case, such as network communication, but it does provide unnecessary functionality that isn't needed. That is, it's like to use a cannon to kill a fly. Shouldn't output be abstracted with minimal methods ? For example, I see that MCStreamer is quite similar to our discussion (with exception of many methods inside), which is not inherited from raw_ostream, MCAsmStreamer, a concrete implementation, does use raw_ostream to output. That said, I do understand your point about "not being necessary", at the same time I think it's better to hide footgun (unneeded and untested methods of raw_ostream) away. API-wise, user still can construct JSONTransport using raw_ostream for convenience. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's not the goal of the output stream abstraction, implementations of raw_ostream describe the capabilities of the stream, it's up to the user to decide which capabilities it wants to take advantage of. That is an example of a good abstraction, because it's reusable across different use-cases and domains. Consider, for example, if we needed to provide an output abstraction for file output for everything that may write to a file. Consider the ease of use today for raw_ostream, if an implementation already exists for your output type (which is the case most often) you have nothing else to do to take advantage of the API. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, I will keep it raw_ostream, will look for some other approach if that won't work for me |
||
public: | ||
explicit JSONTransportOutputOverStream(raw_ostream &out, | ||
bool prettyOutput = false) | ||
: JSONTransportOutput(), out(out), prettyOutput(prettyOutput) {} | ||
|
||
/// Writes the given message to the output stream. | ||
void sendMessage(const llvm::json::Value &msg) final; | ||
|
||
private: | ||
SmallVector<char, 0> outputBuffer; | ||
raw_ostream &out; | ||
/// If the output JSON should be formatted for easier readability. | ||
bool prettyOutput; | ||
}; | ||
|
||
/// A transport class that performs the JSON-RPC communication with the LSP | ||
/// client. | ||
class JSONTransport { | ||
public: | ||
JSONTransport(std::unique_ptr<JSONTransportInput> in, | ||
std::unique_ptr<JSONTransportOutput> out) | ||
: in(std::move(in)), out(std::move(out)) {} | ||
|
||
JSONTransport(std::FILE *in, raw_ostream &out, | ||
JSONStreamStyle style = JSONStreamStyle::Standard, | ||
bool prettyOutput = false) | ||
: in(in), out(out), style(style), prettyOutput(prettyOutput) {} | ||
: in(std::make_unique<JSONTransportInputOverFile>(in, style)), | ||
out(std::make_unique<JSONTransportOutputOverStream>(out, | ||
prettyOutput)) {} | ||
|
||
/// The following methods are used to send a message to the LSP client. | ||
void notify(StringRef method, llvm::json::Value params); | ||
|
@@ -60,30 +132,15 @@ class JSONTransport { | |
/// Start executing the JSON-RPC transport. | ||
llvm::Error run(MessageHandler &handler); | ||
|
||
private: | ||
/// Dispatches the given incoming json message to the message handler. | ||
bool handleMessage(llvm::json::Value msg, MessageHandler &handler); | ||
/// Writes the given message to the output stream. | ||
void sendMessage(llvm::json::Value msg); | ||
|
||
/// Read in a message from the input stream. | ||
LogicalResult readMessage(std::string &json) { | ||
return style == JSONStreamStyle::Delimited ? readDelimitedMessage(json) | ||
: readStandardMessage(json); | ||
} | ||
LogicalResult readDelimitedMessage(std::string &json); | ||
LogicalResult readStandardMessage(std::string &json); | ||
private: | ||
/// The input to read a message from. | ||
std::unique_ptr<JSONTransportInput> in; | ||
|
||
/// An output buffer used when building output messages. | ||
SmallVector<char, 0> outputBuffer; | ||
/// The input file stream. | ||
std::FILE *in; | ||
/// The output file stream. | ||
raw_ostream &out; | ||
/// The JSON stream style to use. | ||
JSONStreamStyle style; | ||
/// If the output JSON should be formatted for easier readability. | ||
bool prettyOutput; | ||
/// The output to send a messages to. | ||
std::unique_ptr<JSONTransportOutput> out; | ||
}; | ||
|
||
//===----------------------------------------------------------------------===// | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasError?
getError
is kind of confusing given the return type.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you're right, I wanted to return error first, but it won't be easy if error type is different for custom implementation.