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