blob: 9acfef109c5617ec2fd4739a39873fe29bb369b8 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# This file is used to generate code in the files
# src/arrowExports.cpp and R/arrowExports.R
#
# This is similar to what compileAttributes() would do,
# with some arrow specific changes.
#
# Core functions are decorated with [[arrow::export]].
# Different decorators can be used to export features that are conditionally
# available.
# Use [[feature::export]] to decorate the functions, and be sure to wrap them in
# #if defined(ARROW_R_WITH_FEATURE)
# Ensure that all machines are sorting the same way
invisible(Sys.setlocale("LC_COLLATE", "C"))
features <- c("acero", "dataset", "substrait", "parquet", "s3", "gcs", "json")
suppressPackageStartupMessages({
library(decor)
library(dplyr)
library(purrr)
library(glue)
library(vctrs)
})
cbind_unnested_functions <- function(data) {
vec_cbind(data, vec_rbind(!!!pull(data, functions)))
}
get_exported_functions <- function(decorations, export_tag) {
out <- decorations |>
filter(decoration %in% paste0(export_tag, "::export")) |>
mutate(functions = map(context, decor:::parse_cpp_function)) |>
cbind_unnested_functions() |>
select(-functions) |>
mutate(decoration = sub("::export", "", decoration))
message(glue(
"*** > {n} functions decorated with [[{tags}::export]]",
n = nrow(out),
tags = paste0(export_tag, collapse = "|")
))
out
}
glue_collapse_data <- function(data, ..., sep = ", ", last = "") {
res <- glue_collapse(glue_data(data, ...), sep = sep, last = last)
if (length(res) == 0) {
res <- ""
}
res
}
wrap_call <- function(name, return_type, args) {
call <- glue::glue("{name}({list_params})", list_params = glue_collapse_data(args, "{name}"))
if (return_type == "void") {
glue::glue("\t{call};\n\treturn R_NilValue;", .trim = FALSE)
} else {
glue::glue("\treturn cpp11::as_sexp({call});")
}
}
feature_available <- function(feat) {
glue::glue(
'extern "C" SEXP _{feat}_available() {{
return Rf_ScalarLogical(
#if defined(ARROW_R_WITH_{toupper(feat)})
TRUE
#else
FALSE
#endif
);
}}
'
)
}
write_if_modified <- function(code, file) {
old <- try(readLines(file), silent = TRUE)
new <- unclass(unlist(strsplit(code, "\n")))
# We don't care about changes in empty lines
if (!identical(old[nzchar(old)], new[nzchar(new)])) {
writeLines(con = file, code)
# To debug why they're different if you think they shouldn't be:
# print(waldo::compare(old[nzchar(old)], new[nzchar(new)]))
message(glue::glue("*** > generated file `{file}`"))
} else {
message(glue::glue("*** > `{file}` not modified"))
}
}
all_decorations <- cpp_decorations()
# Include "arrow" with features here
arrow_exports <- get_exported_functions(all_decorations, c("arrow", features))
arrow_classes <- c(
"Table" = "arrow::Table",
"RecordBatch" = "arrow::RecordBatch"
)
# This takes a cpp11 C wrapper and conditionally makes it available based on
# a feature decoration
ifdef_wrap <- function(cpp11_wrapped, name, sexp_signature, decoration) {
if (identical(decoration, "arrow")) {
# Arrow is now required so don't wrap them
return(cpp11_wrapped)
}
glue(
'
#if defined(ARROW_R_WITH_{toupper(decoration)})
{cpp11_wrapped}
#else
extern "C" SEXP {sexp_signature}{{
\tRf_error("Cannot call {name}(). See https://arrow.apache.org/docs/r/articles/install.html for help installing Arrow C++ libraries. ");
}}
#endif\n\n'
)
}
cpp_functions_definitions <- arrow_exports |>
select(name, return_type, args, file, line, decoration) |>
pmap_chr(function(name, return_type, args, file, line, decoration) {
sexp_params <- glue_collapse_data(args, "SEXP {name}_sexp")
sexp_signature <- glue("_arrow_{name}({sexp_params})")
cpp11_wrapped <- glue(
'
{return_type} {name}({real_params});
extern "C" SEXP {sexp_signature}{{
BEGIN_CPP11
{input_params}{return_line}{wrap_call(name, return_type, args)}
END_CPP11
}}',
sep = "\n",
real_params = glue_collapse_data(args, "{type} {name}"),
input_params = glue_collapse_data(args, "\tarrow::r::Input<{type}>::type {name}({name}_sexp);", sep = "\n"),
return_line = if (nrow(args)) "\n" else ""
)
glue::glue(
"
// {basename(file)}
{ifdef_wrap(cpp11_wrapped, name, sexp_signature, decoration)}
",
sep = "\n"
)
}) |>
glue_collapse(sep = "\n")
cpp_functions_registration <- arrow_exports |>
select(name, return_type, args) |>
pmap_chr(function(name, return_type, args) {
glue('\t\t{{ "_arrow_{name}", (DL_FUNC) &_arrow_{name}, {nrow(args)}}}, ')
}) |>
glue_collapse(sep = "\n")
cpp_file_header <- '// Generated by using data-raw/codegen.R -> do not edit by hand
#include <cpp11.hpp>
#include <cpp11/declarations.hpp>
#include "./arrow_types.h"
'
arrow_exports_cpp <- paste0(
glue::glue(
"
{cpp_file_header}
{cpp_functions_definitions}
\n"
),
glue::glue_collapse(
glue::glue(
"
{feature_available({features})}
"
),
sep = "\n"
),
"
static const R_CallMethodDef CallEntries[] = {
",
glue::glue_collapse(
glue::glue(
'\t\t{{ "_{features}_available", (DL_FUNC)& _{features}_available, 0 }},'
),
sep = "\n"
),
glue::glue(
"\n
{cpp_functions_registration}
\t\t{{NULL, NULL, 0}}
}};
\n"
),
'extern "C" void R_init_arrow(DllInfo* dll){
R_registerRoutines(dll, NULL, CallEntries, NULL, NULL);
R_useDynamicSymbols(dll, FALSE);
arrow::r::altrep::Init_Altrep_classes(dll);
_arrow_compute__Initialize();
}
\n'
)
write_if_modified(arrow_exports_cpp, "src/arrowExports.cpp")
r_functions <- arrow_exports |>
select(name, return_type, args) |>
pmap_chr(function(name, return_type, args) {
params <- if (nrow(args)) {
paste0(", ", glue_collapse_data(args, "{name}"))
} else {
""
}
call <- glue::glue(".Call(`_arrow_{name}`{params})")
if (return_type == "void") {
call <- glue::glue("invisible({call})")
}
glue::glue(
"
{name} <- function({list_params}) {{
{call}
}}
",
list_params = glue_collapse_data(args, "{name}"),
sep = "\n"
)
}) |>
glue_collapse(sep = "\n")
arrow_exports_r <- glue::glue(
"
# Generated by using data-raw/codegen.R -> do not edit by hand
{r_functions}
"
)
write_if_modified(arrow_exports_r, "R/arrowExports.R")