Skip to content

Commit

Permalink
fix(R kernel): Error handling and encoding / decoding of values
Browse files Browse the repository at this point in the history
  • Loading branch information
nokome committed Dec 5, 2021
1 parent 6547dfb commit 0c78b0a
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 17 deletions.
53 changes: 43 additions & 10 deletions rust/kernel-r/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ pub async fn new() -> Result<MicroKernel> {
"r",
("Rscript", "*", &["{{script}}"]),
include_file!("r-kernel.r"),
&[],
"{{name}} <- \"{{json}}\"",
&[include_file!("r-codec.r")],
"{{name}} <- decode_value(\"{{json}}\")",
"{{name}}",
)
.await
Expand All @@ -16,19 +16,52 @@ pub async fn new() -> Result<MicroKernel> {
#[cfg(test)]
mod tests {
use super::*;
use kernel::KernelTrait;
use test_utils::{assert_json_eq, print_logs};
use kernel::{stencila_schema::Node, KernelTrait};
use test_utils::{assert_json_eq, serde_json::json};

/// Tests of basic functionality
/// This test is replicated in all the microkernels.
/// Other test should be written for language specific quirks and regressions.
#[tokio::test]
async fn basic() -> Result<()> {
print_logs();

async fn basics() -> Result<()> {
let mut kernel = new().await?;
kernel.start().await?;

let (outputs, messages) = kernel.exec("6 * 7").await?;
assert_json_eq!(outputs, [42]);
assert!(messages.is_empty());
// Assign a variable and output it
let (outputs, messages) = kernel.exec("a = 2\na").await?;
assert_json_eq!(messages, json!([]));
assert_json_eq!(outputs, [[2]]);

// Print the variable twice and then output it
let (outputs, messages) = kernel.exec("print(a)\nprint(a)\na").await?;
assert_json_eq!(messages, json!([]));
assert_json_eq!(outputs, [[2], [2], [2]]);

// Syntax error
let (outputs, messages) = kernel.exec("bad ^ # syntax").await?;
println!("{:?}", messages);
assert_json_eq!(messages[0].error_type, "SyntaxError");
assert_json_eq!(
messages[0].error_message,
"<text>:2:0: unexpected end of input\n1: bad ^ # syntax\n ^"
);
assert_json_eq!(outputs, json!([]));

// Runtime error
let (outputs, messages) = kernel.exec("foo").await?;
assert_json_eq!(messages[0].error_type, "RuntimeError");
assert_json_eq!(messages[0].error_message, "object 'foo' not found");
assert_json_eq!(outputs, json!([]));

// Set and get another variable
kernel.set("b", Node::Integer(3)).await?;
let b = kernel.get("b").await?;
assert_json_eq!(b, [3]);

// Use both variables
let (outputs, messages) = kernel.exec("a*b").await?;
assert_json_eq!(messages, json!([]));
assert_json_eq!(outputs, [[6]]);

Ok(())
}
Expand Down
16 changes: 16 additions & 0 deletions rust/kernel-r/src/r-codec.r
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
if (!suppressPackageStartupMessages(require("jsonlite", quietly=TRUE)))
install.packages("jsonlite")

decode_value <- function(json) {
jsonlite::fromJSON(json)
}

encode_value <- function(value) {
jsonlite::toJSON(value)
}

encode_message <- function(message, type) {
escaped <- gsub('\\"', '\\\\"', message)
escaped <- gsub('\\n', '\\\\n', escaped)
paste0('{"type":"CodeError","errorType":"', type, '","errorMessage":"', escaped, '"}')
}
34 changes: 27 additions & 7 deletions rust/kernel-r/src/r-kernel.r
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
#!/usr/bin/env Rscript

res_sep <- '\U0010ABBA'
trans_sep <- '\U0010ACDC'
args <- commandArgs(trailingOnly = FALSE)
pattern <- "--file="
match <- grep(pattern, args)
file <- sub(pattern, "", args[match])
dir <- dirname(file)

stdin <- file('stdin', 'r')
source(file.path(dir, "r-codec.r"))

res_sep <- "\U0010ABBA"
trans_sep <- "\U0010ACDC"

print <- function(x, ...) write(paste0(encode_value(x), res_sep), stdout())

message <- function(msg, type) write(paste0(encode_message(msg, type), res_sep), stderr())
info <- function(msg) message(msg, "CodeInfo")
warning <- function(msg) message(msg, "CodeWarning")
error <- function(error, type = "RuntimeError") message(error$message, type)

stdin <- file("stdin", "r")
while (TRUE) {
code <- readLines(stdin, n=1)
unescaped <- gsub("\\\\n", "\n", code)

compiled <- parse(text=unescaped)
out <- eval(compiled)
write(res_sep, stdout())
write(out, stdout())
compiled <- tryCatch(parse(text=unescaped), error=identity)
if (inherits(compiled, "simpleError")) {
error(compiled, "SyntaxError")
} else {
value <- tryCatch(eval(compiled), message=info, warning=warning, error=error)
if (!is.null(value)) {
write(paste0(encode_value(value), res_sep), stdout())
}
}

write(trans_sep, stdout())
write(trans_sep, stderr())
Expand Down

0 comments on commit 0c78b0a

Please sign in to comment.