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: 1e31d3b1db
This commit is contained in:
Maciej Mikołajek 2020-07-21 17:59:28 +02:00 committed by GitHub
parent eb17ad8352
commit e03f109085
6 changed files with 199 additions and 7 deletions

View File

@ -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/

View File

@ -46,6 +46,12 @@ extern "C" {
#[wasm_bindgen(catch)]
fn parse_with_metadata
(content:String) -> std::result::Result<String,JsValue>;
#[wasm_bindgen(catch)]
fn doc_parser_generate_html_source
(content:String) -> std::result::Result<String,JsValue>;
#[wasm_bindgen(catch)]
fn doc_parser_generate_html_from_doc
(content:String) -> std::result::Result<String,JsValue>;
}
/// Wrapper over the JS-compiled parser.
@ -55,10 +61,12 @@ extern "C" {
pub struct Client {}
impl Client {
/// Creates a `Client`
pub fn new() -> Result<Client> {
Ok(Client {})
}
/// Parses Enso code with JS-based parser
pub fn parse(&self, program:String, ids:IdMap) -> api::Result<Ast> {
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<M:api::Metadata>
(&self, program:String) -> api::Result<api::ParsedSourceFile<M>> {
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<String> {
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<String> {
let html_code = || {
let html_code = doc_parser_generate_html_from_doc(code)?;
Result::Ok(html_code)
};
Ok(html_code()?)
}
}

View File

@ -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<RefCell<Client>>);
impl DocParser {
/// Obtains a default doc parser implementation.
#[cfg(not(target_arch = "wasm32"))]
pub fn new() -> api::Result<DocParser> {
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<DocParser> {
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<String> {
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<String> {
self.borrow_mut().generate_html_doc_pure(code)
}
}

View File

@ -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),
}
}

View File

@ -89,8 +89,10 @@ impl From<serde_json::error::Error> 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<Metadata> {
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<ResponseDoc> {
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<M:Metadata>
(&mut self, request: Request) -> Result<Response<M>> {
(&mut self, request:Request) -> Result<Response<M>> {
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<ResponseDoc> {
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<Ast> {
let request = Request::ParseRequest {program,ids};
let response = self.rpc_call::<serde_json::Value>(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<M:Metadata>
(&mut self, program:String) -> api::Result<ParsedSourceFile<M>> {
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<String> {
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<String> {
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)),
}
}

View File

@ -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);
}