Skip to content

Commit

Permalink
deno REPL mode
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelb committed Feb 8, 2022
1 parent 12a1d02 commit 8a95cdf
Show file tree
Hide file tree
Showing 4 changed files with 240 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## v1.2.1
- F# support
- Fix multiline display in floating windows
- Deno brings REPL support for Javascript and TypeScript

## v1.2
- Live mode (a @ChristianChiarulli idea and partial realisation)
Expand Down
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ println!("-> {}", alphabet);
| Go | Bloc | No |
| Haskell | Line | No |
| Java | Bloc | No |
| JavaScript | Bloc | No |
| JavaScript | Bloc | Yes\*\* (Deno)|
| Julia | Bloc | Yes\*\* |
| Lua | Bloc | No |
| Lua-nvim | Bloc | Yes\*\* |
Expand All @@ -449,8 +449,7 @@ println!("-> {}", alphabet);
| Rust | Bloc | No |
| SageMath | Import | Yes\*\* |
| Scala | Bloc | No |
| TypeScript | Bloc | No |

| TypeScript | Bloc | Yes\*\* (Deno)|

Want support for your language? Submit an [issue](https://github.com/michaelb/sniprun/issues/new?assignees=&labels=new-langage-support&template=support-for--language-.md&title=), or even better, [contribute](CONTRIBUTING.md), it's easy!

Expand Down
3 changes: 3 additions & 0 deletions ressources/install_all_compilers_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ sudo apt install gprolog
sudo apt install dotnet
./ressources/go_install.sh
export PATH=$PATH:$HOME/golang/go/bin/

# deno for typescript and javascript
curl -fsSL https://deno.land/x/install/install.sh | sh
241 changes: 234 additions & 7 deletions src/interpreters/JS_TS_deno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,102 @@ pub struct JS_TS_deno {
support_level: SupportLevel,
data: DataHolder,
code: String,
cache_dir: String,

current_output_id: u32,
language_work_dir: String,
main_file_path: String,
}

impl ReplLikeInterpreter for JS_TS_deno {}
impl JS_TS_deno {
fn wait_out_file(
&self,
out_path: String,
err_path: String,
id: u32,
) -> Result<String, SniprunError> {
let end_mark = String::from("sniprun_finished_id=") + &id.to_string();
let start_mark = String::from("sniprun_started_id=") + &id.to_string();

info!(
"searching for things between {:?} and {:?}",
start_mark, end_mark
);

let mut out_contents = String::new();
let mut err_contents = String::new();

let mut pause = std::time::Duration::from_millis(50);
let start = std::time::Instant::now();
loop {
std::thread::sleep(pause);
pause = pause.saturating_add(std::time::Duration::from_millis(50));

// timeout after 30s if no result found
if start.elapsed().as_secs() > 30 {
return Err(SniprunError::InterpreterLimitationError(String::from(
"reached the 30s timeout",
)));
}

//check for stderr first
if let Ok(mut file) = std::fs::File::open(&err_path) {
info!("errfile exists");
out_contents.clear();
let res = file.read_to_string(&mut err_contents);
if res.is_ok() {
// info!("errfile could be read : {:?}", err_contents);
if err_contents.contains(&end_mark) {
if let Some(index) = err_contents.rfind(&start_mark) {
let mut err_to_display = err_contents
[index + start_mark.len()..err_contents.len() - end_mark.len() - 1]
.to_owned();
info!("err to display : {:?}", err_to_display);
if !err_to_display.trim().is_empty() {
info!("err found");
if err_to_display.lines().count() > 2 {
let mut err_to_display_vec =
err_to_display.lines().skip(2).collect::<Vec<&str>>();
err_to_display_vec.dedup();
err_to_display = err_to_display_vec.join("\n");
}

return Err(SniprunError::RuntimeError(err_to_display));
}
}
}
}
}

//check for stdout
if let Ok(mut file) = std::fs::File::open(&out_path) {
info!("file exists");
out_contents.clear();
let res = file.read_to_string(&mut out_contents);
if res.is_ok() {
// info!("out {}", out_contents);
let relevant_content: String = out_contents
.lines()
.filter(|l| !l.contains("undefined"))
.collect::<Vec<&str>>()
.join("\n");
info!("relevant {}", relevant_content);
info!("file could be read : {:?}", relevant_content);
// info!("file : {:?}", contents);
if relevant_content.contains(&end_mark) {
info!("out found");
let index = relevant_content.rfind(&start_mark).unwrap();
return Ok(relevant_content[index + start_mark.len()
..relevant_content.len() - end_mark.len() - 1]
.to_owned());
}
}
}

info!("not found yet");
}
}
}

impl Interpreter for JS_TS_deno {
fn new_with_level(data: DataHolder, support_level: SupportLevel) -> Box<JS_TS_deno> {
Expand All @@ -24,11 +114,13 @@ impl Interpreter for JS_TS_deno {
//pre-create string pointing to main file's and binary's path
let mfp = lwd.clone() + "/main.ts";
Box::new(JS_TS_deno {
cache_dir: lwd.clone() + "/" + &Python3_fifo::get_nvim_pid(&data),
data,
support_level,
code: String::new(),
language_work_dir: lwd,
main_file_path: mfp,
current_output_id: 0,
})
}

Expand Down Expand Up @@ -68,6 +160,13 @@ impl Interpreter for JS_TS_deno {
SupportLevel::Bloc
}

fn behave_repl_like_default() -> bool {
true
}

fn has_repl_capability() -> bool {
true
}

fn fetch_code(&mut self) -> Result<(), SniprunError> {
//note: you probably don't have to modify, or even understand this function
Expand Down Expand Up @@ -95,7 +194,7 @@ impl Interpreter for JS_TS_deno {
}

// now self.code contains the line or bloc of code wanted :-)
info!("Typescript self.code) = {}", self.code);
info!("javascript/typescript self.code) = {}", self.code);
Ok(())
}

Expand All @@ -105,11 +204,11 @@ impl Interpreter for JS_TS_deno {

fn build(&mut self) -> Result<(), SniprunError> {
//write code to file
let mut _file = File::create(&self.main_file_path).expect("failed to create file for typescript_original");
let mut _file =
File::create(&self.main_file_path).expect("failed to create file for js_ts_deno");
// io errors can be ignored, or handled into a proper sniprunerror
// if you panic, it should not be too dangerous for anyone
write(&self.main_file_path, &self.code)
.expect("unable to write to file for typescript_original");
write(&self.main_file_path, &self.code).expect("unable to write to file for js_ts_deno");

Ok(())
}
Expand All @@ -136,22 +235,150 @@ impl Interpreter for JS_TS_deno {
}
}

impl ReplLikeInterpreter for JS_TS_deno {
fn fetch_code_repl(&mut self) -> Result<(), SniprunError> {
if !self.read_previous_code().is_empty() {
// nothing to do, kernel already running
info!("Deno kernel already running");

if let Some(id) = self.get_pid() {
// there is a race condition here but honestly you'd have to
// trigger it on purpose
self.current_output_id = id + 1;
self.set_pid(self.current_output_id);
} else {
info!("Could not retrieve a previous id even if the kernel is running");
info!("This was in saved code: {}", self.read_previous_code());
return Err(SniprunError::CustomError(
"Sniprun failed to connect to the running kernel, please SnipReset".to_string(),
));
}

self.fetch_code()?;
Ok(())
} else {
// launch everything
self.set_pid(0);

let init_repl_cmd = self.data.sniprun_root_dir.clone() + "/ressources/init_repl.sh";
info!(
"launching kernel : {:?} on {:?}",
init_repl_cmd, &self.cache_dir
);

match daemon() {
Ok(Fork::Child) => {
let _res = Command::new("bash")
.args(&[
init_repl_cmd,
self.cache_dir.clone(),
String::from("deno"),
String::from("repl"),
String::from("-q"),
])
.output()
.unwrap();
let pause = std::time::Duration::from_millis(36_000_000);
std::thread::sleep(pause);

return Err(SniprunError::CustomError(
"Timeout expired for python3 REPL".to_owned(),
));
}
Ok(Fork::Parent(_)) => {}
Err(_) => {
info!("JS_TS_deno could not fork itself to the background to launch the kernel")
}
};

let pause = std::time::Duration::from_millis(100);
std::thread::sleep(pause);
self.save_code("kernel_launched\n".to_owned());

Err(SniprunError::CustomError(
"Deno kernel launched, re-run your snippet".to_owned(),
))
}
}

fn add_boilerplate_repl(&mut self) -> Result<(), SniprunError> {
self.add_boilerplate()?;
let start_mark = String::from("\nconsole.log(\"sniprun_started_id=")
+ &self.current_output_id.to_string()
+ "\")\n";
let end_mark = String::from("\nconsole.log(\"sniprun_finished_id=")
+ &self.current_output_id.to_string()
+ "\")\n";
let start_mark_err = String::from("\nconsole.error(\"sniprun_started_id=")
+ &self.current_output_id.to_string()
+ "\")\n";
let end_mark_err = String::from("\nconsole.error(\"sniprun_finished_id=")
+ &self.current_output_id.to_string()
+ "\")\n";

// Removing empty lines
// self.code = self
// .code
// .lines()
// .filter(|l| !l.trim().is_empty())
// .collect::<Vec<&str>>()
// .join("\n");

let all_code = String::from("\n") + &self.code + "\n\n";
self.code = start_mark + &start_mark_err + &all_code + &end_mark + &end_mark_err;
Ok(())
}

fn build_repl(&mut self) -> Result<(), SniprunError> {
self.build()
}

fn execute_repl(&mut self) -> Result<String, SniprunError> {
let send_repl_cmd = self.data.sniprun_root_dir.clone() + "/ressources/launcher_repl.sh";
info!("running launcher {}", send_repl_cmd);
let res = Command::new(send_repl_cmd)
.arg(self.main_file_path.clone())
.arg(self.cache_dir.clone() + "/fifo_repl/pipe_in")
.spawn();
info!("cmd status: {:?}", res);
res.expect("could not run launcher");
// info!("launcher launched : {:?}", res);

let outfile = self.cache_dir.clone() + "/fifo_repl/out_file";
let errfile = self.cache_dir.clone() + "/fifo_repl/err_file";
info!("outfile : {:?}", outfile);
self.wait_out_file(outfile, errfile, self.current_output_id)
}
}

#[cfg(test)]
mod test_ts_js_deno_original {
use super::*;
use serial_test::serial;

#[test]
fn simple_print() {
#[serial(deno)]
fn simple_print() {
let mut data = DataHolder::new();

//inspired from Rust syntax
data.current_bloc = String::from("let message: string = 'Hi';\nconsole.log(message);");
let mut interpreter = JS_TS_deno::new(data);
let res = interpreter.run();
let res = interpreter.run_at_level(SupportLevel::Bloc);

// -> should panic if not an Ok()
let string_result = res.unwrap();

// -> compare result with predicted
assert_eq!(string_result, "Hi\n");
}
#[test]
#[serial(deno)]
fn print_quote() {
let mut data = DataHolder::new();
data.current_bloc = String::from("let message: string = 'Hi';\nconsole.log(message);");
let mut interpreter = Python3_fifo::new(data);
let res = interpreter.run_at_level_repl(SupportLevel::Bloc);
assert!(res.is_err());
}
}

0 comments on commit 8a95cdf

Please sign in to comment.