-
Notifications
You must be signed in to change notification settings - Fork 8
Add ability to return multiple values #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
c03b8e3
b388166
2d20d60
29a6b94
2337a7c
dc229c7
8859703
a4c426f
448c2c7
544bf69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,16 +36,19 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) { | |
| scope = scope | ||
| ) | ||
|
|
||
| # maybe define and allocate the output var | ||
| # maybe define and allocate the output var(s) | ||
| n_protected <- 0L | ||
| return_var <- get(closure_return_var_name(closure), scope) | ||
| if (!return_var@name %in% closure_arg_names) { | ||
| return_var@modified <- TRUE | ||
| assign(return_var@name, return_var, scope) | ||
| append(c_body) <- return_var_c_defs(return_var, fsub@scope) | ||
| add(n_protected) <- 1L # allocated return var | ||
| if (return_var@rank > 1) { | ||
| add(n_protected) <- 1L # allocated _dim_sexp | ||
| return_var_names <- closure_return_var_names(closure) | ||
| return_vars <- mget(return_var_names, scope) | ||
| for (return_var in return_vars) { | ||
| if (!return_var@name %in% closure_arg_names) { | ||
| return_var@modified <- TRUE | ||
| assign(return_var@name, return_var, scope) | ||
| append(c_body) <- return_var_c_defs(return_var, fsub@scope) | ||
|
Comment on lines
+41
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Deduplicate return variables before emitting C defs The loop generates return-variable declarations for every element returned by Useful? React with 👍 / 👎. |
||
| add(n_protected) <- 1L # allocated return var | ||
| if (return_var@rank > 1) { | ||
| add(n_protected) <- 1L # allocated _dim_sexp | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -64,10 +67,49 @@ make_c_bridge <- function(fsub, strict = TRUE, headers = TRUE) { | |
| if (uses_rng) "PutRNGstate();", | ||
| "" | ||
| ) | ||
| if (n_protected > 0) { | ||
| append(c_body) <- glue("UNPROTECT({n_protected});") | ||
| # Determine if the closure returns a list call or a single symbol | ||
| is_list_return <- is_call(last(body(closure)), quote(list)) | ||
|
|
||
| if (length(return_var_names) == 1L && !is_list_return) { | ||
| if (n_protected > 0) { | ||
| append(c_body) <- glue("UNPROTECT({n_protected});") | ||
| } | ||
| append(c_body) <- glue("return {return_var_names};") | ||
| } else { | ||
| return_var_values <- unname(return_var_names) | ||
| provided_names <- names(return_var_names) | ||
| if (is.null(provided_names)) { | ||
| provided_names <- rep("", length(return_var_values)) | ||
| } | ||
| has_any_names <- any(nzchar(provided_names)) | ||
|
|
||
| append(c_body) <- c( | ||
| glue( | ||
| "SEXP _ans = PROTECT(Rf_allocVector(VECSXP, {length(return_var_values)}));" | ||
| ), | ||
| imap(return_var_values, function(nm, i) { | ||
| glue("SET_VECTOR_ELT(_ans, {i-1}, {nm});") | ||
| }) | ||
| ) | ||
|
|
||
| if (has_any_names) { | ||
| names_to_use <- provided_names | ||
| append(c_body) <- c( | ||
| glue( | ||
| "SEXP _names = PROTECT(Rf_allocVector(STRSXP, {length(return_var_values)}));" | ||
| ), | ||
| imap(names_to_use, function(nm, i) { | ||
| glue('SET_STRING_ELT(_names, {i-1}, Rf_mkChar("{nm}"));') | ||
|
Comment on lines
+88
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Escape list element names before embedding in generated C When named multiple return values are handled, the code writes each provided name directly into a C string literal ( Useful? React with 👍 / 👎. |
||
| }), | ||
| "Rf_setAttrib(_ans, R_NamesSymbol, _names);" | ||
| ) | ||
| append(c_body) <- glue("UNPROTECT({n_protected + 2});") | ||
| } else { | ||
| append(c_body) <- glue("UNPROTECT({n_protected + 1});") | ||
| } | ||
|
|
||
| append(c_body) <- "return _ans;" | ||
| } | ||
| append(c_body) <- glue("return {return_var@name};") | ||
|
|
||
| c_args <- paste("SEXP", names(formals(closure)), collapse = ", ") | ||
| c_body <- as_glue(str_flatten_lines(c_body)) | ||
|
|
@@ -416,12 +458,39 @@ as_friendly_size_expression <- function(d) { | |
| deparse1(d) | ||
| } | ||
|
|
||
| closure_return_var_name <- function(closure) { | ||
| return_var_name <- last(body(closure)) | ||
| if (!is.symbol(return_var_name)) { | ||
| stop("return value must be a symbol") | ||
| closure_return_var_names <- function(closure) { | ||
| return_var_expr <- last(body(closure)) | ||
| if (is.symbol(return_var_expr)) { | ||
| val <- as.character(return_var_expr) | ||
| # Return named to keep interface consistent | ||
| return(setNames(val, val)) | ||
| } | ||
| if (is_call(return_var_expr, quote(list))) { | ||
| args <- as.list(return_var_expr)[-1L] | ||
| if (length(args) == 0L) { | ||
| stop("return list must contain at least one element") | ||
| } | ||
| vals <- map_chr(args, as.character) | ||
| nms <- names(args) | ||
| if (is.null(nms)) { | ||
| nms <- rep("", length(vals)) | ||
| } | ||
| # validate names are syntactic when provided | ||
| if (any(nzchar(nms))) { | ||
| bad <- nzchar(nms) & make.names(nms) != nms | ||
| if (any(bad)) { | ||
| stop( | ||
| "only syntactic names are valid, encountered: ", | ||
|
Comment on lines
+480
to
+483
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] Replace invalid paste0 call in non‑syntactic name error When validating names for list returns, the error message uses Useful? React with 👍 / 👎. |
||
| paste0(nms[bad], sep = ", ") | ||
| ) | ||
| } | ||
| } | ||
| # Use provided names when present; fallback to symbol names | ||
| # nms <- ifelse(nzchar(nms), nms, vals) | ||
| return(setNames(vals, nms)) | ||
| } | ||
| as.character(return_var_name) | ||
| ## is it redundent ? new_fortran_subroutine also errors ? | ||
| stop("return value must be a symbol or list of symbols") | ||
| } | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,19 +12,57 @@ defuse_numeric_literals <- function(e) { | |
| e | ||
| } | ||
|
|
||
| # Helper function to validate that all list elements are symbols | ||
| validate_list_symbols <- function(list_call) { | ||
| args <- as.list(list_call)[-1L] | ||
| if (!all(map_lgl(args, is.symbol))) { | ||
| stop("all elements of the list must be symbols") | ||
| } | ||
| invisible(TRUE) | ||
| } | ||
|
|
||
|
|
||
| ensure_last_expr_sym <- function(bdy) { | ||
| if (!is_call(bdy, quote(`{`))) { | ||
| stop("bad body, needs {") | ||
| } | ||
| if (!is.symbol(last_expr <- last(bdy))) { | ||
| bdy[[length(bdy)]] <- call("<-", quote(out_), last_expr) | ||
| bdy[[length(bdy) + 1L]] <- quote(out_) | ||
|
|
||
| last_expr <- last(bdy) | ||
|
|
||
| # Case 1: Last expression is a symbol | ||
| if (is.symbol(last_expr)) { | ||
| # Check for pattern: out <- list(...); out | ||
| n <- length(bdy) | ||
| second_last_expr <- bdy[[n - 1L]] | ||
|
|
||
| list_pattern <- | ||
| is_call(second_last_expr, quote(`<-`)) && | ||
| identical(second_last_expr[[2L]], last_expr) && | ||
| is_call(second_last_expr[[3L]], quote(list)) | ||
|
|
||
| if (list_pattern) { | ||
| # Modify body such that last espression is list(...) | ||
| list_call <- second_last_expr[[3L]] | ||
| validate_list_symbols(list_call) | ||
| bdy[[n - 1]] <- NULL # delete list assignment | ||
| bdy[[n - 1]] <- list_call # replace last line with list(...) | ||
| } | ||
|
|
||
| return(bdy) | ||
| } | ||
|
Comment on lines
+21
to
+52
|
||
|
|
||
| # Case 2: Last expression is a direct list call | ||
| if (is_call(last_expr, quote(list))) { | ||
| validate_list_symbols(last_expr) | ||
| return(bdy) | ||
| } | ||
|
|
||
| # Case 3: Other expressions - create assignment to out_ | ||
| bdy[[length(bdy)]] <- call("<-", quote(out_), last_expr) | ||
| bdy[[length(bdy) + 1L]] <- quote(out_) | ||
| bdy | ||
| } | ||
|
|
||
|
|
||
| whole_doubles_to_ints <- function(x) { | ||
| walker <- function(x) { | ||
| switch( | ||
|
|
@@ -38,7 +76,6 @@ whole_doubles_to_ints <- function(x) { | |
| walker(x) | ||
| } | ||
|
|
||
|
|
||
| substitute_unique_case_insensitive_symbols <- function(x) { | ||
| # TODO: would be nice to fix case-insenstive name clashes | ||
| # with automatic substitutions. Would be a little involved since | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -101,4 +101,3 @@ test_that("expr return value", { | |
| expect_translation_snapshots(fn) | ||
| expect_quick_identical(fn, 1:10) | ||
| }) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P1] De-duplicate return vars before emitting C allocations
The loop that prepares output buffers iterates over every entry returned by
closure_return_var_names()without removing duplicates. If a function returns the same symbol twice (for examplelist(y, y)), the loop generates two identical declarations and PROTECT statements fory, which will fail compilation due to redeclared identifiers and mismatched protection counts. The code should allocate each return variable once and reuse it when packing the list.Useful? React with 👍 / 👎.