compile a file of funtions

This commit is contained in:
Tom Elliott 2024-07-29 13:37:43 +12:00
parent e9751e5c0a
commit f54acd6e8a
13 changed files with 162 additions and 10 deletions

View File

@ -10,3 +10,6 @@ License: MIT + file LICENSE
Encoding: UTF-8 Encoding: UTF-8
Roxygen: list(markdown = TRUE) Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.1 RoxygenNote: 7.3.1
Suggests:
testthat (>= 3.0.0)
Config/testthat/edition: 3

View File

@ -1,9 +1,15 @@
# Generated by roxygen2: do not edit by hand # Generated by roxygen2: do not edit by hand
S3method(print,ts_object) S3method(print,ts_object)
S3method(ts_compile,character)
S3method(ts_compile,default)
S3method(ts_compile,ts_function)
export(ts_character) export(ts_character)
export(ts_compile) export(ts_compile)
export(ts_dataframe)
export(ts_factor)
export(ts_function) export(ts_function)
export(ts_integer) export(ts_integer)
export(ts_list)
export(ts_logical) export(ts_logical)
export(ts_numeric) export(ts_numeric)

View File

@ -1,11 +1,58 @@
#' @export #' @export
ts_compile <- function(f) { ts_compile <- function(f, file = NULL, ...) {
name <- deparse(substitute(f)) UseMethod("ts_compile")
}
#' @export
ts_compile.ts_function <- function(f, file = NULL, name = deparse(substitute(f))) {
inputs <- attr(f, "args") inputs <- attr(f, "args")
result <- attr(f, "result") result <- attr(f, "result")
inputs <- sapply(inputs, \(x) x$type) inputs <- sapply(inputs, \(x) x$type)
fn_args <- paste(names(inputs), inputs, sep = ": ") |> fn_args <- paste(names(inputs), inputs, sep = ": ") |>
paste(collapse = ", ") paste(collapse = ", ")
cat(sprintf("const %s = (%s) => Promise<%s>;", name, fn_args, result$type_fn), "\n") sprintf("const %s = (%s) => Promise<%s>;", name, fn_args, result$type_fn)
}
#' @export
ts_compile.character <- function(
f,
file = sprintf("%s.ts", tools::file_path_sans_ext(f))) {
if (length(f) > 1) {
return(sapply(f, ts_compile))
}
if (!file.exists(f)) {
warning(sprintf("File not found: %s", f))
return()
}
e <- new.env()
source(f, local = e)
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)
cat(
sprintf(
"import type { %s } from 'rserve-ts';\n\n",
paste(types, collapse = ", ")
),
file = file
)
cat(x, sep = "\n", file = file, append = TRUE)
invisible()
}
#' @export
ts_compile.default <- function(f) {
warning("Not supported")
} }

View File

@ -56,7 +56,7 @@ n_type_fun <- function(n, type) {
ts_logical <- function(n = -1L) { ts_logical <- function(n = -1L) {
object( object(
n_type(n, "boolean"), n_type(n, "boolean"),
n_type_fun(n, "Logical"), n_type_fun(n, "RTYPE.Logical"),
check = function(x) { check = function(x) {
if (!is.logical(x)) stop("Expected a boolean") if (!is.logical(x)) stop("Expected a boolean")
if (n > 0 && length(x) != n) stop("Expected a boolean of length ", n) if (n > 0 && length(x) != n) stop("Expected a boolean of length ", n)
@ -69,7 +69,7 @@ ts_logical <- function(n = -1L) {
ts_integer <- function(n = -1L) { ts_integer <- function(n = -1L) {
object( object(
n_type(n, "number"), n_type(n, "number"),
n_type_fun(n, "Integer"), n_type_fun(n, "RTYPE.Integer"),
check = function(x) { check = function(x) {
if (!is.integer(x)) stop("Expected an integer") if (!is.integer(x)) stop("Expected an integer")
if (n > 0 && length(x) != n) stop("Expected an integer of length ", n) if (n > 0 && length(x) != n) stop("Expected an integer of length ", n)
@ -82,7 +82,7 @@ ts_integer <- function(n = -1L) {
ts_numeric <- function(n = -1L) { ts_numeric <- function(n = -1L) {
object( object(
n_type(n, "number"), n_type(n, "number"),
n_type_fun(n, "Numeric"), n_type_fun(n, "RTYPE.Numeric"),
check = function(x) { check = function(x) {
if (!is.numeric(x)) stop("Expected a number", call. = FALSE) if (!is.numeric(x)) stop("Expected a number", call. = FALSE)
if (n > 0 && length(x) != n) { if (n > 0 && length(x) != n) {
@ -97,7 +97,7 @@ ts_numeric <- function(n = -1L) {
ts_character <- function(n = -1L) { ts_character <- function(n = -1L) {
object( object(
n_type(n, "string"), n_type(n, "string"),
n_type_fun(n, "Character"), n_type_fun(n, "RTYPE.Character"),
check = function(x) { check = function(x) {
if (!is.character(x)) stop("Expected a string") if (!is.character(x)) stop("Expected a string")
if (n > 0 && length(x) != n) stop("Expected a string of length ", n) if (n > 0 && length(x) != n) stop("Expected a string of length ", n)

View File

@ -102,5 +102,7 @@ myfun(1:5)
myfun("hello world") myfun("hello world")
ts_compile(myfun) cat(readLines("tests/testthat/app/app.R"), sep = "\n")
ts_compile("tests/testthat/app/app.R", file = "")
``` ```

View File

@ -98,6 +98,17 @@ myfun(1:5)
myfun("hello world") myfun("hello world")
#> Error: Expected a number #> Error: Expected a number
ts_compile(myfun) cat(readLines("tests/testthat/app/app.R"), sep = "\n")
#> const myfun = (x: number | number[]) => Promise<Robj.Numeric<1>)>; #> 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)
#> )
ts_compile("tests/testthat/app/app.R", file = "")
#> import type { Character, Numeric } from 'rserve-ts';
#>
#> const fn_first = (x: string | string[]) => Promise<Character<1>)>;
#> const fn_mean = (x: number | number[]) => Promise<Numeric<1>)>;
``` ```

25
man/object.Rd Normal file
View File

@ -0,0 +1,25 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/types.R
\name{object}
\alias{object}
\title{Typed object}
\usage{
object(
type = "any",
type_fn = "any",
default = NULL,
check = function() stop("Not implemented")
)
}
\arguments{
\item{type}{The type of the object that Typescript expect to send to R.}
\item{type_fn}{The type of the object that Typescript expects to recieve from R.}
\item{default}{The default value of the object.}
\item{check}{A function that checks the object and returns it if it is valid. This operates on the R side and is mostly for development and debugging purposes. It is up to the developer to ensure that all functions return the correct type of object always.}
}
\description{
This is the base type for all typed objects. It is not meant to be used directly.
}

11
man/ts_dataframe.Rd Normal file
View File

@ -0,0 +1,11 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/types.R
\name{ts_dataframe}
\alias{ts_dataframe}
\title{Typed dataframe}
\usage{
ts_dataframe(...)
}
\description{
This is essentially a list, but the elements must have names and are all the same length.
}

14
man/ts_factor.Rd Normal file
View File

@ -0,0 +1,14 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/types.R
\name{ts_factor}
\alias{ts_factor}
\title{Typed factor}
\usage{
ts_factor(levels = NULL)
}
\arguments{
\item{levels}{A character vector of levels (optional).}
}
\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!).
}

11
man/ts_list.Rd Normal file
View File

@ -0,0 +1,11 @@
% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/types.R
\name{ts_list}
\alias{ts_list}
\title{Typed list}
\usage{
ts_list(values = NULL)
}
\description{
A list is a vector of other robjects, which may or may not be named.
}

12
tests/testthat.R Normal file
View File

@ -0,0 +1,12 @@
# This file is part of the standard setup for testthat.
# It is recommended that you do not modify it.
#
# Where should you do additional test configuration?
# Learn more about the roles of various files in:
# * https://r-pkgs.org/testing-design.html#sec-tests-files-overview
# * https://testthat.r-lib.org/articles/special-files.html
library(testthat)
library(ts)
test_check("ts")

6
tests/testthat/app/app.R Normal file
View File

@ -0,0 +1,6 @@
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)
)

View File

@ -0,0 +1,4 @@
import { types } from 'rserve-ts';
const fn_first = (x: string | string[]) => Promise<RTYPE.Character<1>)>;
const fn_mean = (x: number | number[]) => Promise<RTYPE.Numeric<1>)>;