diff --git a/R/JSContext.R b/R/JSContext.R index 481d78f..f2f74ff 100644 --- a/R/JSContext.R +++ b/R/JSContext.R @@ -1,3 +1,71 @@ +#' Assess validity of JS code without evaluating +#' +#' @name JSContext-method-validate +#' @aliases validate +#' +#' @usage validate(code_string) +#' +#' @description +#' Checks whether JS code string is valid code in the current context +#' +#' @param code_string The JS code to check +#' @return A boolean indicating whether code is valid +#' +#' @examples +#' \dontrun{ +#' ctx <- JSContext$new() +#' ctx$validate("1 + 2") +#' } +validate <- NULL + +#' Evaluate JS string or file in the current context +#' +#' @name JSContext-method-source +#' @aliases source +#' +#' @usage source(file = NULL, code = NULL) +#' +#' @description +#' Evaluate a provided JavaScript file or string within the initialised context. +#' Note that this method should only be used for initialising functions or values +#' within the context, no values are returned from this function. See the `$call()` +#' method for returning values. +#' +#' @param file A path to the JavaScript file to load +#' @param code A single string of JavaScript to evaluate +#' @return No return value, called for side effects +#' +#' @examples +#' \dontrun{ +#' ctx <- JSContext$new() +#' ctx$source(file = "path/to/file.js") +#' ctx$source(code = "1 + 2") +#' } +source <- NULL + +#' Call a JS function in the current context +#' +#' @name JSContext-method-call +#' @aliases call +#' +#' @usage call(function_name, ...) +#' +#' @description Call a specified function in the JavaScript context with the +#' provided arguments. +#' +#' @param function_name The function to be called +#' @param ... The arguments to be passed to the function +#' @return The result of calling the specified function, +#' the return type is mapped from JS to R using `jsonlite::fromJSON()` +#' +#' @examples +#' \dontrun{ +#' ctx <- JSContext$new() +#' ctx$source(code = "function add(a, b) { return a + b; }") +#' ctx$call("add", 1, 2) +#' } +call <- NULL + new_JSContext <- function(stack_size = NULL) { stack_size_int = ifelse(is.null(stack_size), -1, stack_size) rt_and_ctx = qjs_context(stack_size_int) diff --git a/R/qjs.R b/R/qjs.R index f9a0320..fd044f9 100644 --- a/R/qjs.R +++ b/R/qjs.R @@ -29,7 +29,8 @@ qjs_source <- function(ctx_ptr, code_string) { } qjs_call <- function(ctx_ptr, function_name, ...) { - parse_return(.Call(`qjs_call_`, ctx_ptr, function_name, list(...))) + args_json <- lapply(list(...), jsonlite::toJSON, auto_unbox = TRUE) + parse_return(.Call(`qjs_call_`, ctx_ptr, function_name, args_json)) } qjs_validate <- function(ctx_ptr, function_name) { @@ -41,11 +42,12 @@ qjs_validate <- function(ctx_ptr, function_name) { #' Use the QuickJS C API to convert an R object to a JSON string #' #' @param arg Argument to convert to JSON +#' @param auto_unbox Automatically unbox single element vectors #' @return JSON string #' #' @export -to_json <- function(arg) { - .Call(`to_json_`, arg) +to_json <- function(arg, auto_unbox = FALSE) { + .Call(`to_json_`, arg, auto_unbox) } #' from_json diff --git a/inst/include/quickjsr/JSON_to_JSValue.hpp b/inst/include/quickjsr/JSON_to_JSValue.hpp index b8efe73..708bd44 100644 --- a/inst/include/quickjsr/JSON_to_JSValue.hpp +++ b/inst/include/quickjsr/JSON_to_JSValue.hpp @@ -7,22 +7,12 @@ namespace quickjsr { JSValue JSON_to_JSValue(JSContext* ctx, const std::string& json) { - JSValue global = JS_GetGlobalObject(ctx); - JSValue json_obj = JS_GetPropertyStr(ctx, global, "JSON"); - JSValue parse = JS_GetPropertyStr(ctx, json_obj, "parse"); - - JSValue json_str = JS_NewString(ctx, json.c_str()); - JSValue result = JS_Call(ctx, parse, global, 1, &json_str); + JSValue result = JS_ParseJSON(ctx, json.c_str(), json.size(), ""); if (JS_IsException(result)) { js_std_dump_error(ctx); } - JS_FreeValue(ctx, json_str); - JS_FreeValue(ctx, parse); - JS_FreeValue(ctx, json_obj); - JS_FreeValue(ctx, global); - return result; } diff --git a/inst/include/quickjsr/JSValue_to_JSON.hpp b/inst/include/quickjsr/JSValue_to_JSON.hpp index 44121e6..317fb4d 100644 --- a/inst/include/quickjsr/JSValue_to_JSON.hpp +++ b/inst/include/quickjsr/JSValue_to_JSON.hpp @@ -6,12 +6,9 @@ namespace quickjsr { -std::string JSValue_to_JSON(JSContext* ctx, JSValue* val) { - JSValue global = JS_GetGlobalObject(ctx); - JSValue json = JS_GetPropertyStr(ctx, global, "JSON"); - JSValue stringify = JS_GetPropertyStr(ctx, json, "stringify"); +std::string JSValue_to_JSON(JSContext* ctx, JSValue val) { - JSValue result_js = JS_Call(ctx, stringify, global, 1, val); + JSValue result_js = JS_JSONStringify(ctx, val, JS_UNDEFINED, JS_UNDEFINED); std::string result; if (JS_IsException(result_js)) { js_std_dump_error(ctx); @@ -21,9 +18,6 @@ std::string JSValue_to_JSON(JSContext* ctx, JSValue* val) { } JS_FreeValue(ctx, result_js); - JS_FreeValue(ctx, stringify); - JS_FreeValue(ctx, json); - JS_FreeValue(ctx, global); return result; } diff --git a/man/JSContext-method-call.Rd b/man/JSContext-method-call.Rd new file mode 100644 index 0000000..8a64002 --- /dev/null +++ b/man/JSContext-method-call.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/JSContext.R +\docType{data} +\name{JSContext-method-call} +\alias{JSContext-method-call} +\alias{call} +\title{Call a JS function in the current context} +\format{ +An object of class \code{NULL} of length 0. +} +\usage{ +call(function_name, ...) +} +\arguments{ +\item{function_name}{The function to be called} + +\item{...}{The arguments to be passed to the function} +} +\value{ +The result of calling the specified function, +the return type is mapped from JS to R using \code{jsonlite::fromJSON()} +} +\description{ +Call a specified function in the JavaScript context with the +provided arguments. +} +\examples{ +\dontrun{ +ctx <- JSContext$new() +ctx$source(code = "function add(a, b) { return a + b; }") +ctx$call("add", 1, 2) +} +} +\keyword{datasets} diff --git a/man/JSContext-method-source.Rd b/man/JSContext-method-source.Rd new file mode 100644 index 0000000..ca98d13 --- /dev/null +++ b/man/JSContext-method-source.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/JSContext.R +\docType{data} +\name{JSContext-method-source} +\alias{JSContext-method-source} +\alias{source} +\title{Evaluate JS string or file in the current context} +\format{ +An object of class \code{NULL} of length 0. +} +\usage{ +source(file = NULL, code = NULL) +} +\arguments{ +\item{file}{A path to the JavaScript file to load} + +\item{code}{A single string of JavaScript to evaluate} +} +\value{ +No return value, called for side effects +} +\description{ +Evaluate a provided JavaScript file or string within the initialised context. +Note that this method should only be used for initialising functions or values +within the context, no values are returned from this function. See the \verb{$call()} +method for returning values. +} +\examples{ +\dontrun{ +ctx <- JSContext$new() +ctx$source(file = "path/to/file.js") +ctx$source(code = "1 + 2") +} +} +\keyword{datasets} diff --git a/man/JSContext-method-validate.Rd b/man/JSContext-method-validate.Rd new file mode 100644 index 0000000..b24fa13 --- /dev/null +++ b/man/JSContext-method-validate.Rd @@ -0,0 +1,29 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/JSContext.R +\docType{data} +\name{JSContext-method-validate} +\alias{JSContext-method-validate} +\alias{validate} +\title{Assess validity of JS code without evaluating} +\format{ +An object of class \code{NULL} of length 0. +} +\usage{ +validate(code_string) +} +\arguments{ +\item{code_string}{The JS code to check} +} +\value{ +A boolean indicating whether code is valid +} +\description{ +Checks whether JS code string is valid code in the current context +} +\examples{ +\dontrun{ +ctx <- JSContext$new() +ctx$validate("1 + 2") +} +} +\keyword{datasets} diff --git a/man/to_json.Rd b/man/to_json.Rd index a5d3787..dcd23b3 100644 --- a/man/to_json.Rd +++ b/man/to_json.Rd @@ -4,10 +4,12 @@ \alias{to_json} \title{to_json} \usage{ -to_json(arg) +to_json(arg, auto_unbox = FALSE) } \arguments{ \item{arg}{Argument to convert to JSON} + +\item{auto_unbox}{Automatically unbox single element vectors} } \value{ JSON string diff --git a/src/init.cpp b/src/init.cpp index e1e3563..7712a38 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -10,7 +10,7 @@ extern "C" { SEXP qjs_validate_(SEXP ctx_ptr_, SEXP code_string_); SEXP qjs_call_(SEXP ctx_ptr_, SEXP function_name_, SEXP args_json_); SEXP qjs_eval_(SEXP eval_string_); - SEXP to_json_(SEXP arg_); + SEXP to_json_(SEXP arg_, SEXP auto_unbox_); SEXP from_json_(SEXP json_); @@ -20,7 +20,7 @@ extern "C" { {"qjs_eval_", (DL_FUNC) &qjs_eval_, 1}, {"qjs_source_", (DL_FUNC) &qjs_source_, 2}, {"qjs_validate_", (DL_FUNC) &qjs_validate_, 2}, - {"to_json_", (DL_FUNC) &to_json_, 1}, + {"to_json_", (DL_FUNC) &to_json_, 2}, {"from_json_", (DL_FUNC) &from_json_, 1}, {NULL, NULL, 0} }; diff --git a/src/quickjsr.cpp b/src/quickjsr.cpp index 221ea6a..943df1a 100644 --- a/src/quickjsr.cpp +++ b/src/quickjsr.cpp @@ -65,7 +65,7 @@ extern "C" SEXP qjs_call_(SEXP ctx_ptr_, SEXP fun_name_, SEXP args_list_) { int n_args = Rf_length(args_list_); std::vector args(n_args); for (int i = 0; i < n_args; i++) { - args[i] = quickjsr::SEXP_to_JSValue(ctx, VECTOR_ELT(args_list_, i), true); + args[i] = quickjsr::JSON_to_JSValue(ctx, cpp11::as_cpp(VECTOR_ELT(args_list_, i))); } JSValue global = JS_GetGlobalObject(ctx); @@ -77,7 +77,7 @@ extern "C" SEXP qjs_call_(SEXP ctx_ptr_, SEXP fun_name_, SEXP args_list_) { js_std_dump_error(ctx); result = "Error!"; } else { - result = quickjsr::JSValue_to_JSON(ctx, &result_js); + result = quickjsr::JSValue_to_JSON(ctx, result_js); } JS_FreeValue(ctx, result_js); @@ -103,7 +103,7 @@ extern "C" SEXP qjs_eval_(SEXP eval_string_) { js_std_dump_error(ctx); result = "Error!"; } else { - result = quickjsr::JSValue_to_JSON(ctx, &val); + result = quickjsr::JSValue_to_JSON(ctx, val); } JS_FreeValue(ctx, val); @@ -114,13 +114,14 @@ extern "C" SEXP qjs_eval_(SEXP eval_string_) { END_CPP11 } -extern "C" SEXP to_json_(SEXP arg_) { +extern "C" SEXP to_json_(SEXP arg_, SEXP auto_unbox_) { BEGIN_CPP11 JSRuntime* rt = JS_NewRuntime(); JSContext* ctx = JS_NewContext(rt); - JSValue arg = quickjsr::SEXP_to_JSValue(ctx, arg_); - std::string result = quickjsr::JSValue_to_JSON(ctx, &arg); + JSValue arg = quickjsr::SEXP_to_JSValue(ctx, arg_, + cpp11::as_cpp(auto_unbox_)); + std::string result = quickjsr::JSValue_to_JSON(ctx, arg); JS_FreeValue(ctx, arg); JS_FreeContext(ctx);