The nomenclature of the package, ast2ast, is rooted in the abbreviation ast, signifying abstract syntax tree. Originally I planned to convert the abstract syntax tree of R to the C++ tree as the literature recommended for transpilers. However, through iterative refinement, a more optimal methodology emerged. The development incorporated an Expression Template Library in C++ known as ETR, meticulously crafted to mimic R. Thus, R code is translated into ETR code, which is subsequently compiled. The original ETR library is accessible at https://github.com/Konrad1991/ETR. It’s imperative to note that the version integrated into ast2ast has undergone substantial enhancements, amplifying its efficacy and adaptability.
Displayed below is a basic bubble sort function implemented in R on
the left, juxtaposed with its ETR counterpart on the right. It is
obvious that the two code snippets are quite similar. Remarkably, the
overall structure of the R code remains unaltered. Instead, the
substitution of individual functions with their ETR equivalents forms
the crux of the transformation.
In the C++ code, all functions are located in the etr
namespace. Certain functions share identical names in both R and C++,
such as the length function. To mitigate potential conflicts,
these calls are modified to explicitly reference the etr namespace,
resulting in expressions like etr::length. Other functions as
for example : and [ cannot
be defined in C++ (at least not in the way they are used in R) therefore
they are replaced by functions with new names e.g. etr::colon
and etr::subset.
Additionally, C++ necessitates explicit declaration of variable types.
Within this example for all variables the type Array<Double,
Buffer
bubbleSort <- function(a) {
size <- length(a)
for (i in 1:size) {
for (j in 1:(size - 1)) {
if (a[j] > a[j + 1]) {
temp <- a[j]
a[j] <- a[j + 1]
a[j + 1] <- temp
}
}
}
return(a)
}
// [[Rcpp::depends(ast2ast)]]
// [[Rcpp::plugins(cpp2a)]]
#include "etr.hpp"
// [[Rcpp::export]]
SEXP bubbleSort(SEXP aSEXP) {
etr::Aetr::Double, etr::Buffer<etr::Double>> a = aSEXP;
int size;
etr::Array<etr::Double, etr::Buffer<etr::Double>> temp;
size = etr::length(a);
for(const auto& i : etr::colon(1, size)) {
for(const auto& j : etr::colon(1, (size - 1.0))) {
if (etr::subset(a, j) > etr::subset(a, j + 1.0)) {
temp = etr::subset(a, j);
etr::subset(a, j) = etr::subset(a, j + 1.0);
etr::subset(a, j + 1.0) = temp;
};
}
}
return(etr::Cast(a));
}
The XPtr interfaces creates an external pointer which can be used in other C++ programs.
f <- function(a, b) {
c <- a + b
return(c)
}
f_args <- function(a, b) {
a |> type(vec(double)) |> ref()
b |> type(vec(double)) |> ref()
}
fcpp <- ast2ast::translate(f, f_args, output = "XPtr")
// [[Rcpp::depends(RcppArmadillo)]]
// [[Rcpp::depends(ast2ast)]]
// [[Rcpp::plugins(cpp2a)]]
#include "etr.hpp"
typedef etr::Array<etr::Double, etr::Buffer<etr::Double>> (*FP)(etr::Array<etr::Double, etr::Buffer<etr::Double>>& a, etr::Array<etr::Double, etr::Buffer<etr::Double>>& b);
// [[Rcpp::export]]
void call_xptr(Rcpp::XPtr<FP> ep) {
FP f = *ep;
etr::Array<etr::Double, etr::Buffer<etr::Double>> a;
etr::Array<etr::Double, etr::Buffer<etr::Double>> b;
etr::Array<etr::Double, etr::Buffer<etr::Double>> c;
a = etr::c(etr::Double(1), etr::Double(2), etr::Double(3));
b = etr::c(etr::Double(4), etr::Double(5), etr::Double(6));
c = f(a, b);
etr::print(c);
}
call_xptr(fcpp)
## 5 7 9