Skip to contents

Takes an R expression (in the sense of rlang::is_expression()) and translates it into a character string giving the equivalent expression in another programming language, according to the supplied rules.

This function is experimental.

Usage

translate(expr, rules, env = parent.frame())

Arguments

expr

Expression or list of expressions to be translated.

rules

Which rules to follow. You can pass a string from among "C", "C++", "Lua", or "R", or a list with translation rules (see Details).

env

Environment for injections in expr (see expression).

Value

The translated expression as a single character string.

Details

The parameter rules can be a character string naming a "built-in" ruleset. Otherwise, rules should be a list with the following elements:

  • ops: an unnamed list of operator definitions, each of which should be a list with four elements:

    • arity the number of operands

    • prec the precedence of the operator (lower numbers equal higher precedence)

    • assoc the associativity of the operator, either "LTR", "RTL", or anything else for no associativity

    • str a glue::glue() format string with {A[1]}, {A[2]}, etc., standing in for the first, second, etc. operands.

    • nopar a numeric vector with indices of arguments to the operator which should never be enclosed in parentheses. The default and usual value is integer(0), but (for example) it can be 2 for the [ operator, as parentheses within the second argument (the content of the brackets) are redundant.

    The function elixir:::op can help to assemble such lists.

  • paren a glue::glue() format string with {x} standing in for the enclosed expression. Describes how parentheses are expressed in the target language. Example: "({x})" is correct for virtually all programming languages.

  • symbol: a function which takes a symbol and returns a character string, representing the name of that symbol in the target language. This could just be equal to base::as.character, but it can be changed to something else in case you want name mangling, or e.g. some processing to replace . in symbols with some other character (as . are often not allowed as part of symbols in popular languages).

  • literal: a named list in which the name refers to the class of the operand to translate, and the value should be a function of a single argument (the operand) returning a character string.

It may be helpful to inspect elixir:::ruleset to clarify the above format.

There are some important shortcomings to translate(). Here are some potential pitfalls:

  • Named arguments are not supported, because we cannot translate an R function call like mean(x, na.rm = TRUE) without knowing which parameter of mean matches to na.rm.

  • Division: An R expression like 1/3 gets translated into 1./3. in C/C++, as numeric literals are coerced to type double. So both of these evaluate to 0.333. However, the R expression 1L/3L will get translated into 1/3 in C/C++, which evaluates to 0 (as it is integer division).

  • Modulo: R uses "Knuth's modulo", where a %% b has the same sign as b. Lua also uses Knuth's modulo, but C/C++ use "truncated modulo", where a % b has the same sign as a. (see Wikipedia for details). So when converting a modulo expression from R to C/C++, a call to the function kmod is generated in the C/C++ expression. This is not a standard library function, so you will have to provide a definition yourself. A workable definition is: double kmod(double x, double y) { double r = fmod(x, y); return r && r < 0 != y < 0 ? r + y : r; } (R) or a % b (Lua)

  • Types: In R, the type of a %% b and of a %/% b depends on the type of a and b (if both are integers, the result is an integer; if at least one is numeric, the result is numeric).

  • Chained assignment does not work in Lua.

Examples

translate({x ^ y}, "C++")
#> pow(x, y)