From e03f1090851bbf88f658ec9ae7dd6e6278476144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Miko=C5=82ajek?= Date: Tue, 21 Jul 2020 17:59:28 +0200 Subject: [PATCH] Connect Doc Parser and generator to IDE (https://github.com/enso-org/ide/pull/675) * up parser version * js client conn * up parser * test with parser right before integration * checkpoint [skip ci] * save state * tests * changes * rev * Adam Review * requested changes * Ability to generate Docstr from pure doc code * bump Original commit: https://github.com/enso-org/ide/commit/1e31d3b1dbea1668ffa33bf30f466b478f7a6eae --- gui/src/rust/ide/lib/parser/build.rs | 2 +- gui/src/rust/ide/lib/parser/src/jsclient.rs | 27 +++++++++ gui/src/rust/ide/lib/parser/src/lib.rs | 50 ++++++++++++++++ gui/src/rust/ide/lib/parser/src/main.rs | 27 ++++++++- gui/src/rust/ide/lib/parser/src/wsclient.rs | 63 ++++++++++++++++++-- gui/src/rust/ide/lib/parser/tests/doc-gen.rs | 37 ++++++++++++ 6 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 gui/src/rust/ide/lib/parser/tests/doc-gen.rs diff --git a/gui/src/rust/ide/lib/parser/build.rs b/gui/src/rust/ide/lib/parser/build.rs index 9a6058ab43f..5b6a1751125 100644 --- a/gui/src/rust/ide/lib/parser/build.rs +++ b/gui/src/rust/ide/lib/parser/build.rs @@ -25,7 +25,7 @@ use std::path::PathBuf; const PARSER_PATH: &str = "./pkg/scala-parser.js"; /// Commit from `enso` repository that will be used to obtain parser from. -const PARSER_COMMIT: &str = "157582b81edb1e02a91e7866fde7e0ae72d5570f"; +const PARSER_COMMIT: &str = "84e565e8bc9575d95d12739df0cf9bd50d77af62"; /// Magic code that needs to be prepended to ScalaJS generated parser due to: /// https://github.com/scala-js/scala-js/issues/3677/ diff --git a/gui/src/rust/ide/lib/parser/src/jsclient.rs b/gui/src/rust/ide/lib/parser/src/jsclient.rs index f2b81008377..02d0f6b43ae 100644 --- a/gui/src/rust/ide/lib/parser/src/jsclient.rs +++ b/gui/src/rust/ide/lib/parser/src/jsclient.rs @@ -46,6 +46,12 @@ extern "C" { #[wasm_bindgen(catch)] fn parse_with_metadata (content:String) -> std::result::Result; + #[wasm_bindgen(catch)] + fn doc_parser_generate_html_source + (content:String) -> std::result::Result; + #[wasm_bindgen(catch)] + fn doc_parser_generate_html_from_doc + (content:String) -> std::result::Result; } /// Wrapper over the JS-compiled parser. @@ -55,10 +61,12 @@ extern "C" { pub struct Client {} impl Client { + /// Creates a `Client` pub fn new() -> Result { Ok(Client {}) } + /// Parses Enso code with JS-based parser pub fn parse(&self, program:String, ids:IdMap) -> api::Result { let ast = || { let json_ids = serde_json::to_string(&ids)?; @@ -69,6 +77,7 @@ impl Client { Ok(ast()?) } + /// Parses Enso code with metadata pub fn parse_with_metadata (&self, program:String) -> api::Result> { let result = || { @@ -78,4 +87,22 @@ impl Client { }; Ok(result()?) } + + /// Calls JS doc parser to generate HTML from documented Enso code + pub fn generate_html_docs(&self, program:String) -> api::Result { + let html_code = || { + let html_code = doc_parser_generate_html_source(program)?; + Result::Ok(html_code) + }; + Ok(html_code()?) + } + + /// Calls JS doc parser to generate HTML from pure doc code w/o Enso's AST + pub fn generate_html_doc_pure(&self, code:String) -> api::Result { + let html_code = || { + let html_code = doc_parser_generate_html_from_doc(code)?; + Result::Ok(html_code) + }; + Ok(html_code()?) + } } diff --git a/gui/src/rust/ide/lib/parser/src/lib.rs b/gui/src/rust/ide/lib/parser/src/lib.rs index 0dd37a9ba2f..e32ebf13294 100644 --- a/gui/src/rust/ide/lib/parser/src/lib.rs +++ b/gui/src/rust/ide/lib/parser/src/lib.rs @@ -120,3 +120,53 @@ impl Parser { } } } + + + +// ========================================== +// === Documentation Parser and Generator === +// ========================================== + +/// Handle to a doc parser implementation. +/// +/// Currently this component is implemented as a wrapper over documentation +/// parser written in Scala. Depending on compilation target (native or wasm) +/// it uses either implementation provided by `wsclient` or `jsclient`. +#[derive(Clone,CloneRef,Debug,Shrinkwrap)] +#[shrinkwrap(mutable)] +pub struct DocParser(pub Rc>); + +impl DocParser { + /// Obtains a default doc parser implementation. + #[cfg(not(target_arch = "wasm32"))] + pub fn new() -> api::Result { + let client = wsclient::Client::new()?; + let doc_parser = Rc::new(RefCell::new(client)); + Ok(DocParser(doc_parser)) + } + + /// Obtains a default doc parser implementation. + #[cfg(target_arch = "wasm32")] + pub fn new() -> api::Result { + let client = jsclient::Client::new()?; + let doc_parser = Rc::new(RefCell::new(client)); + Ok(DocParser(doc_parser)) + } + + /// Obtains a default doc parser implementation, panicking in case of failure. + pub fn new_or_panic() -> DocParser { + DocParser::new().unwrap_or_else(|e| panic!("Failed to create doc parser: {:?}", e)) + } + + /// Parses program with documentation and generates HTML code. + /// If the program does not have any documentation will return empty string. + pub fn generate_html_docs(&self, program:String) -> api::Result { + self.borrow_mut().generate_html_docs(program) + } + + /// Parses pure documentation code and generates HTML code. + /// Will return empty string for empty entry + pub fn generate_html_doc_pure(&self, code:String) -> api::Result { + self.borrow_mut().generate_html_doc_pure(code) + } +} diff --git a/gui/src/rust/ide/lib/parser/src/main.rs b/gui/src/rust/ide/lib/parser/src/main.rs index 27ca6e96ca7..4a40dfc1314 100644 --- a/gui/src/rust/ide/lib/parser/src/main.rs +++ b/gui/src/rust/ide/lib/parser/src/main.rs @@ -3,7 +3,8 @@ use enso_prelude::*; /// Simple interactive tester - calls parser with its argument (or a -/// hardcoded default) and prints the result +/// hardcoded default) and prints the result, then calls doc parser +/// and prints the HTML code or an error message. fn main() { let default_input = String::from("import Foo.Bar\nfoo = a + 2"); let program = std::env::args().nth(1).unwrap_or(default_input); @@ -15,4 +16,28 @@ fn main() { Ok(result) => println!("Parser responded with: {:?}", result), Err(e) => println!("Failed to obtain a response: {:?}", e), } + + + let default_input = String::from("##\n DEPRECATED\n Foo bar baz\ntype Foo\n type Bar"); + let program = std::env::args().nth(1).unwrap_or(default_input); + println!("Will parse: {}", program); + + let parser = parser::DocParser::new_or_panic(); + let output = parser.generate_html_docs(program); + match output { + Ok(result) => println!("Doc parser responded with: {:?}", result), + Err(e) => println!("Failed to obtain a response: {:?}", e), + } + + + let default_input = String::from("Computes the _logical_ conjunction of *two* booleans"); + let program = std::env::args().nth(1).unwrap_or(default_input); + println!("Will parse: {}", program); + + let parser = parser::DocParser::new_or_panic(); + let output = parser.generate_html_doc_pure(program); + match output { + Ok(result) => println!("Doc parser responded with: {:?}", result), + Err(e) => println!("Failed to obtain a response: {:?}", e), + } } diff --git a/gui/src/rust/ide/lib/parser/src/wsclient.rs b/gui/src/rust/ide/lib/parser/src/wsclient.rs index cfa64b7f42a..5c9541cb33d 100644 --- a/gui/src/rust/ide/lib/parser/src/wsclient.rs +++ b/gui/src/rust/ide/lib/parser/src/wsclient.rs @@ -89,8 +89,10 @@ impl From for Error { /// All request supported by the Parser Service. #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum Request { - ParseRequest { program: String, ids: IdMap }, - ParseRequestWithMetadata { content: String }, + ParseRequest { program : String, ids : IdMap }, + ParseRequestWithMetadata { content : String }, + DocParserGenerateHtmlSource { program : String }, + DocParserGenerateHtmlFromDoc { code : String }, } /// All responses that Parser Service might reply with. @@ -100,6 +102,13 @@ pub enum Response { Error { message : String }, } +/// All responses that Doc Parser Service might reply with. +#[derive(Debug, serde::Deserialize)] +pub enum ResponseDoc { + SuccessDoc { code : String }, + Error { message : String }, +} + // ============ @@ -165,15 +174,37 @@ mod internal { } } + /// Obtains a text message from peer and deserializes it using JSON + /// into a `ResponseDoc`. + /// + /// Should be called exactly once after each `send_request` invocation. + pub fn recv_response_doc(&mut self) -> Result { + let response = self.connection.recv_message()?; + match response { + websocket::OwnedMessage::Text(code) => Ok(serde_json::from_str(&code)?), + _ => Err(Error::NonTextResponse(response)), + } + } + /// Sends given `Request` to peer and receives a `Response`. /// /// Both request and response are exchanged in JSON using text messages /// over WebSocket. pub fn rpc_call - (&mut self, request: Request) -> Result> { + (&mut self, request:Request) -> Result> { self.send_request(request)?; self.recv_response() } + + /// Sends given `Request` to peer and receives a `ResponseDoc`. + /// + /// Both request and response are exchanged in JSON using text messages + /// over WebSocket. + pub fn rpc_call_doc + (&mut self, request:Request) -> Result { + self.send_request(request)?; + self.recv_response_doc() + } } } @@ -196,22 +227,44 @@ impl Client { Ok(client) } + /// Sends a request to parser service to parse Enso code pub fn parse(&mut self, program:String, ids:IdMap) -> api::Result { let request = Request::ParseRequest {program,ids}; let response = self.rpc_call::(request)?; match response { Response::Success {module} => Ok(module.ast.into()), - Response::Error {message} => Err(ParsingError(message)), + Response::Error {message} => Err(ParsingError(message)), } } + /// Sends a request to parser service to parse code with metadata pub fn parse_with_metadata (&mut self, program:String) -> api::Result> { let request = Request::ParseRequestWithMetadata {content:program}; let response = self.rpc_call(request)?; match response { Response::Success {module} => Ok(module), - Response::Error {message} => Err(ParsingError(message)), + Response::Error {message} => Err(ParsingError(message)), + } + } + + /// Sends a request to parser service to generate HTML code from documented Enso code + pub fn generate_html_docs(&mut self, program:String) -> api::Result { + let request = Request::DocParserGenerateHtmlSource {program}; + let response_doc = self.rpc_call_doc(request)?; + match response_doc { + ResponseDoc::SuccessDoc {code} => Ok(code), + ResponseDoc::Error {message} => Err(ParsingError(message)), + } + } + + /// Sends a request to parser service to generate HTML code from pure documentation code + pub fn generate_html_doc_pure(&mut self, code:String) -> api::Result { + let request = Request::DocParserGenerateHtmlFromDoc {code}; + let response_doc = self.rpc_call_doc(request)?; + match response_doc { + ResponseDoc::SuccessDoc {code} => Ok(code), + ResponseDoc::Error {message} => Err(ParsingError(message)), } } diff --git a/gui/src/rust/ide/lib/parser/tests/doc-gen.rs b/gui/src/rust/ide/lib/parser/tests/doc-gen.rs new file mode 100644 index 00000000000..1d62546b20e --- /dev/null +++ b/gui/src/rust/ide/lib/parser/tests/doc-gen.rs @@ -0,0 +1,37 @@ +use parser::DocParser; + +use wasm_bindgen_test::wasm_bindgen_test_configure; +use wasm_bindgen_test::wasm_bindgen_test; + + + +wasm_bindgen_test_configure!(run_in_browser); + +#[wasm_bindgen_test] +fn doc_gen_test() { + // no doc case + let input = String::from("type Foo\n type Bar"); + let program = std::env::args().nth(1).unwrap_or(input); + let parser = DocParser::new_or_panic(); + let gen_code = parser.generate_html_docs(program).unwrap(); + assert_eq!(gen_code.len(), 0); + + // only doc case + let input = String::from("Foo *Bar* Baz"); + let program = std::env::args().nth(1).unwrap_or(input); + let parser = DocParser::new_or_panic(); + let gen_code = parser.generate_html_doc_pure(program).unwrap(); + assert_ne!(gen_code.len(), 0); + + let input = String::from("##\n foo\ntype Foo\n"); + let program = std::env::args().nth(1).unwrap_or(input); + let parser = DocParser::new_or_panic(); + let gen_code = parser.generate_html_docs(program).unwrap(); + assert_ne!(gen_code.len(), 0); + + let input = String::from("##\n DEPRECATED\n Foo bar baz\ntype Foo\n type Bar"); + let program = std::env::args().nth(1).unwrap_or(input); + let parser = DocParser::new_or_panic(); + let gen_code = parser.generate_html_docs(program).unwrap(); + assert_ne!(gen_code.len(), 0); +}