docs
This commit is contained in:
parent
7dfe4c3c50
commit
5e84656a89
54
R/compile.R
54
R/compile.R
@ -1,17 +1,27 @@
|
||||
#' Compile R functions to TypeScript schemas
|
||||
#' @param f A function or file path
|
||||
#' @param name The name of the function
|
||||
#' @param ... Additional arguments
|
||||
#' @param file The file path to write the TypeScript schema (optional). If `""`, the output is printed to the standard output console (see `cat`).
|
||||
#' @return Character vector of TypeScript schema, or NULL if writing to file
|
||||
#' @md
|
||||
#' @export
|
||||
ts_compile <- function(f, ..., file = NULL) {
|
||||
ts_compile <- function(f, ..., name, file) {
|
||||
o <- UseMethod("ts_compile")
|
||||
}
|
||||
|
||||
#' @export
|
||||
ts_compile.ts_function <- function(f, name = deparse(substitute(f)), ...) {
|
||||
inputs <- attr(f, "args")
|
||||
result <- attr(f, "result")
|
||||
ts_compile.ts_function <- function(f, ..., name = deparse(substitute(f))) {
|
||||
inputs <- f$args
|
||||
result <- f$result
|
||||
|
||||
inputs <- sapply(inputs, \(x) x$zod_type)
|
||||
fn_args <- paste(inputs) |>
|
||||
paste(collapse = ", ")
|
||||
sprintf("const %s = R.ocap([%s], %s]);", name, fn_args, result$r_type)
|
||||
inputs <- sapply(inputs, \(x) x$input_type)
|
||||
fn_args <- paste(paste(inputs), collapse = ", ")
|
||||
|
||||
sprintf(
|
||||
"const %s = Robj.ocap([%s], %s);", name, fn_args,
|
||||
result$return_type
|
||||
)
|
||||
}
|
||||
|
||||
#' @export
|
||||
@ -32,22 +42,22 @@ ts_compile.character <- function(
|
||||
x <- sapply(ls(e), \(x) ts_compile(e[[x]], file = file, name = x))
|
||||
|
||||
# find any RTYPE.[type] and grab types
|
||||
types <- unique(
|
||||
gsub(
|
||||
"RTYPE\\.(\\w+)", "\\1",
|
||||
unlist(regmatches(x, gregexpr("RTYPE\\.\\w+", x)))
|
||||
)
|
||||
)
|
||||
x <- gsub("RTYPE\\.", "", x)
|
||||
# types <- unique(
|
||||
# gsub(
|
||||
# "RTYPE\\.(\\w+)", "\\1",
|
||||
# unlist(regmatches(x, gregexpr("RTYPE\\.\\w+", x)))
|
||||
# )
|
||||
# )
|
||||
# x <- gsub("RTYPE\\.", "", x)
|
||||
|
||||
cat(
|
||||
sprintf(
|
||||
"import { %s } from 'rserve-ts';\n\n",
|
||||
paste(types, collapse = ", ")
|
||||
),
|
||||
file = file
|
||||
src <- c(
|
||||
"import { Robj } from 'rserve-ts';",
|
||||
"import { z } from 'zod';",
|
||||
"\n",
|
||||
x
|
||||
)
|
||||
cat(x, sep = "\n", file = file, append = TRUE)
|
||||
|
||||
writeLines(src, file)
|
||||
|
||||
invisible()
|
||||
}
|
||||
|
||||
50
R/types.R
50
R/types.R
@ -136,7 +136,15 @@ n_type_fun <- function(n, type) {
|
||||
sprintf("%s(%s)", type, ifelse(n < 0, "", n))
|
||||
}
|
||||
|
||||
#' Logical or boolean type
|
||||
#'
|
||||
#' Booleans are represented in Zod schema as either a boolean (`z.boolean()`),
|
||||
#' or a typed Uint8Array (`z.instanceof(Uint8Array)`).
|
||||
#'
|
||||
#' @param n The length of the boolean vector. If `n = 1` then a single boolean is expected. If `n = 0` then any length is expected. If `n > 1` then a boolean vector of length `n` is expected.
|
||||
#' @return A ts object that accepts logical scalars or vectors of length `n`.
|
||||
#' @export
|
||||
#' @md
|
||||
ts_logical <- function(n = -1L) {
|
||||
ts_object(
|
||||
n_type(n, "z.boolean()"),
|
||||
@ -151,10 +159,18 @@ ts_logical <- function(n = -1L) {
|
||||
)
|
||||
}
|
||||
|
||||
#' Integer type
|
||||
#'
|
||||
#' Integers are represented in Zod schema as either a number (`z.number()`),
|
||||
#' or a Int32Array (`z.instanceof(Int32Array)`).
|
||||
#'
|
||||
#' @param n The length of the integer vector. If `n = 1` then a single integer is expected. If `n = 0` then any length is expected. If `n > 1` then an integer vector of length `n` is expected.
|
||||
#' @return A ts object that accepts integer scalars or vectors of length `n`.
|
||||
#' @export
|
||||
#' @md
|
||||
ts_integer <- function(n = -1L) {
|
||||
ts_object(
|
||||
n_type(n, "z.number()"),
|
||||
n_type(n, "z.number()", "z.instanceof(Int32Array)"),
|
||||
n_type_fun(n, "Robj.integer"),
|
||||
check = function(x) {
|
||||
if (!is.integer(x)) stop("Expected an integer")
|
||||
@ -166,7 +182,15 @@ ts_integer <- function(n = -1L) {
|
||||
)
|
||||
}
|
||||
|
||||
#' Numeric type
|
||||
#'
|
||||
#' Numbers are represented in Zod schema as either a number (`z.number()`),
|
||||
#' or a Float64Array (`z.instanceof(Float64Array)`).
|
||||
#'
|
||||
#' @param n The length of the numeric vector. If `n = 1` then a single number is expected. If `n = 0` then any length is expected. If `n > 1` then a numeric vector of length `n` is expected.
|
||||
#' @return A ts object that accepts numeric scalars or vectors of length `n`.
|
||||
#' @export
|
||||
#' @md
|
||||
ts_numeric <- function(n = -1L) {
|
||||
ts_object(
|
||||
n_type(n, "z.number()"),
|
||||
@ -181,7 +205,14 @@ ts_numeric <- function(n = -1L) {
|
||||
)
|
||||
}
|
||||
|
||||
#' Character or string type
|
||||
#'
|
||||
#' Strings are represented in Zod schema as either a string (`z.string()`),
|
||||
#' or a string array (`z.array(z.string())`).
|
||||
#' @param n The length of the string vector. If `n = 1` then a single string is expected. If `n = 0` then any length is expected. If `n > 1` then a string vector of length `n` is expected.
|
||||
#' @return A ts object that accepts strings or string vectors of length `n`.
|
||||
#' @export
|
||||
#' @md
|
||||
ts_character <- function(n = -1L) {
|
||||
ts_object(
|
||||
n_type(n, "z.string()"),
|
||||
@ -203,6 +234,7 @@ vector_as_ts_array <- function(x) {
|
||||
#' Factors are integers with labels. On the JS side, these are *always* represented as a string array (even if only one value - yay!).
|
||||
#'
|
||||
#' @param levels A character vector of levels (optional).
|
||||
#' @return A ts object that accepts factors with the specified levels.
|
||||
#'
|
||||
#' @export
|
||||
#' @md
|
||||
@ -236,6 +268,7 @@ ts_factor <- function(levels = NULL) {
|
||||
#'
|
||||
#' A list is a vector of other robjects, which may or may not be named.
|
||||
#' @param ... A list of types, named or unnamed.
|
||||
#' @return A ts object that accepts lists with the specified types.
|
||||
#'
|
||||
#' @export
|
||||
#' @md
|
||||
@ -292,6 +325,9 @@ ts_list <- function(...) {
|
||||
#'
|
||||
#' This is essentially a list, but the elements must have names and are all the same length.
|
||||
#'
|
||||
#' @param ... Named types.
|
||||
#' @return A ts object that accepts data frames with the specified types.
|
||||
#'
|
||||
#' @export
|
||||
#' @md
|
||||
ts_dataframe <- function(...) {
|
||||
@ -323,7 +359,13 @@ ts_dataframe <- function(...) {
|
||||
)
|
||||
}
|
||||
|
||||
#' Null type
|
||||
#'
|
||||
#' This is a type that only accepts `NULL`.
|
||||
#'
|
||||
#' @return A ts object that only accepts `NULL`.
|
||||
#' @export
|
||||
#'
|
||||
ts_null <- function() {
|
||||
ts_object(
|
||||
"z.null()",
|
||||
@ -335,7 +377,13 @@ ts_null <- function() {
|
||||
)
|
||||
}
|
||||
|
||||
#' Void type
|
||||
#'
|
||||
#' This is a type that accepts null values (this would typically be used for
|
||||
#' functions that return nothing).
|
||||
#' @return A ts object that accepts `NULL`.
|
||||
#' @export
|
||||
#' @md
|
||||
ts_void <- function() {
|
||||
ts_object(
|
||||
"z.void()",
|
||||
|
||||
10
README.Rmd
10
README.Rmd
@ -107,13 +107,3 @@ cat(readLines("tests/testthat/app.R"), sep = "\n")
|
||||
|
||||
ts_compile("tests/testthat/app.R", file = "")
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] Add support for more types
|
||||
- [ ] Allow generic types (e.g., `<T>(x: T) => T`)
|
||||
- [ ] Add support for conditional return types
|
||||
|
||||
e.g., `const sample = <T, N extends number>(x: T[], n: N) => N extends 1 ? T : T[]`
|
||||
|
||||
- [ ] Function overloads? Perhaps just a wrapper around several function definitions...
|
||||
|
||||
18
man/ts_character.Rd
Normal file
18
man/ts_character.Rd
Normal file
@ -0,0 +1,18 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/types.R
|
||||
\name{ts_character}
|
||||
\alias{ts_character}
|
||||
\title{Character or string type}
|
||||
\usage{
|
||||
ts_character(n = -1L)
|
||||
}
|
||||
\arguments{
|
||||
\item{n}{The length of the string vector. If \code{n = 1} then a single string is expected. If \code{n = 0} then any length is expected. If \code{n > 1} then a string vector of length \code{n} is expected.}
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts strings or string vectors of length \code{n}.
|
||||
}
|
||||
\description{
|
||||
Strings are represented in Zod schema as either a string (\code{z.string()}),
|
||||
or a string array (\code{z.array(z.string())}).
|
||||
}
|
||||
23
man/ts_compile.Rd
Normal file
23
man/ts_compile.Rd
Normal file
@ -0,0 +1,23 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/compile.R
|
||||
\name{ts_compile}
|
||||
\alias{ts_compile}
|
||||
\title{Compile R functions to TypeScript schemas}
|
||||
\usage{
|
||||
ts_compile(f, ..., name, file)
|
||||
}
|
||||
\arguments{
|
||||
\item{f}{A function or file path}
|
||||
|
||||
\item{...}{Additional arguments}
|
||||
|
||||
\item{name}{The name of the function}
|
||||
|
||||
\item{file}{The file path to write the TypeScript schema (optional). If \code{""}, the output is printed to the standard output console (see \code{cat}).}
|
||||
}
|
||||
\value{
|
||||
Character vector of TypeScript schema, or NULL if writing to file
|
||||
}
|
||||
\description{
|
||||
Compile R functions to TypeScript schemas
|
||||
}
|
||||
@ -6,6 +6,12 @@
|
||||
\usage{
|
||||
ts_dataframe(...)
|
||||
}
|
||||
\arguments{
|
||||
\item{...}{Named types.}
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts data frames with the specified types.
|
||||
}
|
||||
\description{
|
||||
This is essentially a list, but the elements must have names and are all the same length.
|
||||
}
|
||||
|
||||
@ -9,6 +9,9 @@ ts_factor(levels = NULL)
|
||||
\arguments{
|
||||
\item{levels}{A character vector of levels (optional).}
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts factors with the specified levels.
|
||||
}
|
||||
\description{
|
||||
Factors are integers with labels. On the JS side, these are \emph{always} represented as a string array (even if only one value - yay!).
|
||||
}
|
||||
|
||||
18
man/ts_integer.Rd
Normal file
18
man/ts_integer.Rd
Normal file
@ -0,0 +1,18 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/types.R
|
||||
\name{ts_integer}
|
||||
\alias{ts_integer}
|
||||
\title{Integer type}
|
||||
\usage{
|
||||
ts_integer(n = -1L)
|
||||
}
|
||||
\arguments{
|
||||
\item{n}{The length of the integer vector. If \code{n = 1} then a single integer is expected. If \code{n = 0} then any length is expected. If \code{n > 1} then an integer vector of length \code{n} is expected.}
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts integer scalars or vectors of length \code{n}.
|
||||
}
|
||||
\description{
|
||||
Integers are represented in Zod schema as either a number (\code{z.number()}),
|
||||
or a Int32Array (\code{z.instanceof(Int32Array)}).
|
||||
}
|
||||
@ -9,6 +9,9 @@ ts_list(...)
|
||||
\arguments{
|
||||
\item{...}{A list of types, named or unnamed.}
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts lists with the specified types.
|
||||
}
|
||||
\description{
|
||||
A list is a vector of other robjects, which may or may not be named.
|
||||
}
|
||||
|
||||
18
man/ts_logical.Rd
Normal file
18
man/ts_logical.Rd
Normal file
@ -0,0 +1,18 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/types.R
|
||||
\name{ts_logical}
|
||||
\alias{ts_logical}
|
||||
\title{Logical or boolean type}
|
||||
\usage{
|
||||
ts_logical(n = -1L)
|
||||
}
|
||||
\arguments{
|
||||
\item{n}{The length of the boolean vector. If \code{n = 1} then a single boolean is expected. If \code{n = 0} then any length is expected. If \code{n > 1} then a boolean vector of length \code{n} is expected.}
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts logical scalars or vectors of length \code{n}.
|
||||
}
|
||||
\description{
|
||||
Booleans are represented in Zod schema as either a boolean (\code{z.boolean()}),
|
||||
or a typed Uint8Array (\code{z.instanceof(Uint8Array)}).
|
||||
}
|
||||
14
man/ts_null.Rd
Normal file
14
man/ts_null.Rd
Normal file
@ -0,0 +1,14 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/types.R
|
||||
\name{ts_null}
|
||||
\alias{ts_null}
|
||||
\title{Null type}
|
||||
\usage{
|
||||
ts_null()
|
||||
}
|
||||
\value{
|
||||
A ts object that only accepts \code{NULL}.
|
||||
}
|
||||
\description{
|
||||
This is a type that only accepts \code{NULL}.
|
||||
}
|
||||
18
man/ts_numeric.Rd
Normal file
18
man/ts_numeric.Rd
Normal file
@ -0,0 +1,18 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/types.R
|
||||
\name{ts_numeric}
|
||||
\alias{ts_numeric}
|
||||
\title{Numeric type}
|
||||
\usage{
|
||||
ts_numeric(n = -1L)
|
||||
}
|
||||
\arguments{
|
||||
\item{n}{The length of the numeric vector. If \code{n = 1} then a single number is expected. If \code{n = 0} then any length is expected. If \code{n > 1} then a numeric vector of length \code{n} is expected.}
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts numeric scalars or vectors of length \code{n}.
|
||||
}
|
||||
\description{
|
||||
Numbers are represented in Zod schema as either a number (\code{z.number()}),
|
||||
or a Float64Array (\code{z.instanceof(Float64Array)}).
|
||||
}
|
||||
15
man/ts_void.Rd
Normal file
15
man/ts_void.Rd
Normal file
@ -0,0 +1,15 @@
|
||||
% Generated by roxygen2: do not edit by hand
|
||||
% Please edit documentation in R/types.R
|
||||
\name{ts_void}
|
||||
\alias{ts_void}
|
||||
\title{Void type}
|
||||
\usage{
|
||||
ts_void()
|
||||
}
|
||||
\value{
|
||||
A ts object that accepts \code{NULL}.
|
||||
}
|
||||
\description{
|
||||
This is a type that accepts null values (this would typically be used for
|
||||
functions that return nothing).
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
library(ts)
|
||||
|
||||
fn_mean <- ts_function(mean, x = ts_numeric(), result = ts_numeric(1))
|
||||
fn_first <- ts_function(function(x) x[1],
|
||||
x = ts_character(-1), result = ts_character(1)
|
||||
fn_first <- ts_function(function(x = ts_character(-1)) x[1],
|
||||
result = ts_character(1)
|
||||
)
|
||||
|
||||
sample_num <- ts_function(
|
||||
|
||||
17
tests/testthat/app.d.ts
vendored
17
tests/testthat/app.d.ts
vendored
@ -1,5 +1,14 @@
|
||||
import type { Character, Numeric, PE.Numeric<1>, } from 'rserve-ts';
|
||||
import { Robj } from "rserve-ts";
|
||||
import { z } from "zod";
|
||||
|
||||
const fn_first = (x: string | string[]) => Promise<Character<1>)>;
|
||||
const fn_mean = (x: number | number[]) => Promise<Numeric<1>)>;
|
||||
c("const sample_one = (x: number | number[]) => Promise<Numeric<1>)>;", "const sample_one = (x: string | string[]) => Promise<Character<1>)>;")
|
||||
const fn_first = Robj.ocap(
|
||||
[z.union([z.string(), Robj.character(0)])],
|
||||
Robj.character(1)
|
||||
);
|
||||
|
||||
const fn_mean = Robj.ocap(
|
||||
[z.union([z.number(), z.instanceof(Float64Array)])],
|
||||
Robj.numeric(1)
|
||||
);
|
||||
|
||||
const sample_num = Robj.ocap([z.instanceof(Float64Array)], Robj.numeric(1));
|
||||
|
||||
@ -1,14 +0,0 @@
|
||||
# overload input/return types
|
||||
|
||||
|
||||
|
||||
|
||||
# ts_compile(d_normal)
|
||||
|
||||
# compile to:
|
||||
# const sampler = R.ocap(
|
||||
# [],
|
||||
# R.list({
|
||||
# num: R.ocap([], R.numeric(1))
|
||||
# })
|
||||
# );
|
||||
44
tests/testthat/test-compile.R
Normal file
44
tests/testthat/test-compile.R
Normal file
@ -0,0 +1,44 @@
|
||||
test_that("anonomous functions", {
|
||||
add <- ts_function(
|
||||
function(a = ts_numeric(1), b = ts_numeric(1)) a + b,
|
||||
result = ts_numeric(1)
|
||||
)
|
||||
|
||||
ts_compile(add)
|
||||
|
||||
# expect_equal(add$call(1, 2), 3)
|
||||
# expect_error(add$call("a", 2))
|
||||
})
|
||||
|
||||
test_that("Complex functions", {
|
||||
get_sample <- ts_function(
|
||||
function(n = ts_numeric(1)) {
|
||||
sample(values, n)
|
||||
},
|
||||
result = ts_numeric()
|
||||
)
|
||||
|
||||
sampler <- ts_function(
|
||||
function(values = ts_numeric()) {
|
||||
list(
|
||||
get = get_sample$copy(),
|
||||
set = ts_function(
|
||||
function(value = ts_numeric()) {
|
||||
values <<- value
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
result = ts_list(
|
||||
get = get_sample,
|
||||
set = ts_function(NULL, value = ts_numeric())
|
||||
)
|
||||
)
|
||||
|
||||
ts_compile(sampler)
|
||||
})
|
||||
|
||||
test_that("Compile files", {
|
||||
on.exit(if (file.exists("app.d.ts")) unlink("app.d.ts"))
|
||||
res <- ts_compile("app.R")
|
||||
})
|
||||
@ -65,8 +65,4 @@ test_that("function with complex return types", {
|
||||
|
||||
expect_silent(s$set$call(100:200))
|
||||
expect_gte(s$get$call(1), 100)
|
||||
|
||||
# you would then 'deploy' this as an App that
|
||||
# doesn't require the $call methods
|
||||
# e.g., sampler(1:10)$get(5)
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user