org.math.R.R2jsSession Maven / Gradle / Ivy
package org.math.R;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.api.scripting.ScriptUtils;
/**
* This class evaluate an R expression by parsing it in javascript expression and then
* evaluate the javascript expression with the Java ScriptEngine. This class
* uses external javascript library like mathjs to evaluate expressions.
*
*
* ------------------------------ Supported and unsupported syntaxes ------------------------------------------------------------------
* ## Basic syntaxes
* - affectation (=, <-)
* - mathematical expression (pi, ^, **, <, <=, >, >=, !=, ==, ++, +=, -=, priority of operators, for, for in, if, else, range)
* ## Functions
* - All syntaxes of functions in R
* - recursive functions
* - Mathematical functions ("abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "exp", "floor", "log", "max", "min", "round", "sin", "sqrt", "tan" )
* - NOT SUPPORTED: - function with named argument (ex: "f(y=1.23,x=4.56)") (impossible to support in the ES5)
* - multiple functions in one command (impossible just write multiple lines)
* ## Arrays
* - definition of arrays (c(1,2,3), c(0:4), array(0.0,c(4,3)))
* - accessor: a[1],
* - operations (+, -, *, /)
* - function: length
* - NOT SUPPORTED: %*%, %/% : create matrices instead of arrays to use theses operations
* ## Matrices
* - definition of matrices with: nrow, ncol, byrow
* - operations (transpose, +, -, *, ^, %*%, %/%, %%)
* - column and row selection ([1,], [,1], [,], [c(1,2),])
* - function supported: determinant, solve, dim
* - TO SUPPORT: eigen
* ## DataFrames
* - constructor: data.frame(first=a,second=b), data.frame('first'=a,'second'=b), data.frame(a,b)
* - accessor: '$', "[['element']]"
* NOT SUPPORTED: column extraction "[1:2,]"
* ## Lists
* - constructor: list(first=a,second=b), list('first'=a,'second'=b), list(a,b)
* - accessor: '$', "[['element']]"
* NOT SUPPORTED: column extraction "[1:2,]", accessor "[[1]]"
* ## R functions
* - write.csv
* - runif
* - save and load variables
* - ls
* - rm variables
* - cbind (on matrices only)
* - rbind (on matrices only)
* - file.exists
* - savels
* TO SUPPORT: rnorm, capture.output
* NOT SUPPORTED: toPNG, asHTML, cbind (array and dataframe), rbind (array and dataframe), multiple imbricated functions
*
* -----------------------------------------------------------------------------------------------------------------------------------
*
* @author Nicolas Chabalier
*/
public class R2jsSession extends Rsession implements RLog {
private static final String[] MATH_FUN_JS = { "abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "exp", "floor",
"log", "max", "min", "round", "sin", "sqrt", "tan", "sign", "sum","mean", "median", "std", "var" };
private static final String[] MATH_CONST_JS = { "pi" };
// JavaScript libraries used to evaluate expression
private static final String MATH_JS_FILE = "/org/math/R/math.js";
private static final String R_JS_FILE = "/org/math/R/R.js";
private static final String RAND_JS_FILE = "/org/math/R/rand.js";
// private static final String PLOT_JS_FILE = "/org/math/R/plotly.js";
public ScriptEngine js;
private static final String ENVIRONMENT_DEFAULT = "__r2js__";
private static final String THIS_ENVIRONMENT = "__this__";
public static final String[] DISABLED_FUNCTIONS = new String[]{"png", "plot", "abline", "rgb", "hist", "pairs", "lines", "points"};
// Set of global variables declared
public Set variablesSet;
public Set functionsSet;
// List of quotes expression
private List quotesList;
// Map containing js libraries already loaded (to not reload them at each instance of R2jsSession)
private final static Map jsLibraries = new HashMap<>();
public static R2jsSession newInstance(final RLog console, Properties properties) {
return new R2jsSession(console, properties);
}
public R2jsSession(RLog console, Properties properties) {
this(console, properties, null);
}
/**
* Default constructor
Initialize the Javascript js and load external js libraries
*
* @param console - console
* @param properties - properties
* @param environmentName - name of the environment
*/
public R2jsSession(RLog console, Properties properties, String environmentName) {
super(console);
if (environmentName != null) {
envName = "__" + environmentName + "__";
} else {
envName = ENVIRONMENT_DEFAULT;
}
variablesSet = new HashSet<>();
functionsSet = new HashSet<>();
TRY_MODE_DEFAULT = false;
ScriptEngineManager manager = new ScriptEngineManager();
js = manager.getEngineByName("js");
// Load external js libraries used by the js to evaluate expressions
try {
loadJSLibraries();
for (String f : DISABLED_FUNCTIONS)
addReturnNullFunction(f);
// Instantiate the variables storage object which store all variables defined in the current session
js.eval("var " + envName + " = math.clone({});");
//js.eval("var " + envName + " = {};");
js.eval(THIS_ENVIRONMENT + " = " + envName);
} catch (ScriptException ex) {
Logger.getLogger(R2jsSession.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
}
public R2jsSession(final PrintStream p, Properties properties) {
this(p, properties, null);
}
public R2jsSession(final PrintStream p, Properties properties, String environmentName) {
this(new RLog() {
public void log(String string, Level level) {
PrintStream pp = null;
if (p != null) {
pp = p;
} else {
pp = System.err;
}
if (level == Level.WARNING) {
pp.print("(!) ");
} else if (level == Level.ERROR) {
pp.print("(!!) ");
}
pp.println(string);
}
public void closeLog() {
if (p != null) {
p.close();
}
}
}, properties, environmentName);
}
public void addReturnNullFunction(String name) throws ScriptException {
js.eval("function " + name + "(a,b,c,d,e,f) {return null;}");
functionsSet.add(name);
}
public void addJSFunction(String name, String js) throws ScriptException {
this.js.eval("function " + name + (js.startsWith("function")?js.substring("function".length()):js));
functionsSet.add(name);
}
/**
* Load external js libraries to evaluate js expresions:
* - 'math.js' : evaluate all mathematical expressions with numbers, arrays and matrices.
* - 'r.js': contains various function ( loading and saving files/variables, range function, ...)
*
* @throws ScriptException
*/
private synchronized void loadJSLibraries() throws ScriptException {
// Loading math.JS
if(!jsLibraries.containsKey("math")) {
InputStream mathInputStream = this.getClass().getResourceAsStream(MATH_JS_FILE);
js.eval(new InputStreamReader(mathInputStream));
jsLibraries.put("math", js.get("math"));
} else {
js.put("math", jsLibraries.get("math"));
}
js.eval("var parser = math.parser();");
// Change 'Matrix' mathjs config by 'Array'
js.eval("math.config({matrix: 'Array'})");
js.eval("var str = String.prototype;");
// Loading rand.js
if(!jsLibraries.containsKey("rand")) {
InputStream randInputStream = this.getClass().getResourceAsStream(RAND_JS_FILE);
js.eval(new InputStreamReader(randInputStream));
js.eval("rand = rand()");
jsLibraries.put("rand", js.get("rand"));
} else {
js.put("rand", jsLibraries.get("rand"));
}
// Loading plotly.js
// InputStream RInputStream = this.getClass().getResourceAsStream(PLOT_JS_FILE);
// js.eval(new InputStreamReader(RInputStream));
// js.eval("Plotly = Plotly()");
// Loading R.js
if(!jsLibraries.containsKey("R")) {
InputStream RInputStream = this.getClass().getResourceAsStream(R_JS_FILE);
js.eval(new InputStreamReader(RInputStream));
js.eval("R = R()");
jsLibraries.put("R", js.get("R"));
} else {
js.put("R", jsLibraries.get("R"));
}
}
static final String POINT_CHAR_JS_KEY = "__";
/**
* Convert an R expression in a Js expression WARNING: many R syntaxes are not supported yet
*
* @param e - the R expression
* @return the js script expression
*/
public static String nameRtoJs(String e) {
// Replace "." char by a dedicated key
if (e.contains(POINT_CHAR_JS_KEY)) {
throw new IllegalArgumentException("Cannot use " + POINT_CHAR_JS_KEY + " in expression (reserved substring)");
}
e = e.replace(".", POINT_CHAR_JS_KEY);
return e;
}
public static Properties R_TO_JS = new Properties();
static {
R_TO_JS.put("R.version.string", "'R2js'");
R_TO_JS.put(".GlobalEnv", ENVIRONMENT_DEFAULT);
R_TO_JS.put("stop(", "throw new Error(");
R_TO_JS.put("as.numeric(", "asNumeric(");
R_TO_JS.put("as.integer(", "asInteger(");
R_TO_JS.put("as.matrix(", "matrix(");
R_TO_JS.put("as.array(", "array(");
R_TO_JS.put("which.min(", "whichMin(");
R_TO_JS.put("which.max(", "whichMax(");
R_TO_JS.put("print(", "_print(");
R_TO_JS.put("is.function(", "isFunction(");
R_TO_JS.put("is.null(", "isNull(");
R_TO_JS.put("Sys.sleep(", "SysSleep(");
R_TO_JS.put("capture.output(", "_print("); //should use the output stream capture instead...
R_TO_JS.put("NA", "null");
R_TO_JS.put("new.env()", "{}");
R_TO_JS.put("dev.off()", "");
R_TO_JS.put("return()", "return(NULL)");
R_TO_JS.put("...", "varargs");
}
final static String AW = "((\\A)|(\\W)|(\\())(";
final static String Az = ")((\\W)|(\\z)|(\\)))";
public boolean debug_js = false;
DecimalFormat formatter = new DecimalFormat("#.#############",DecimalFormatSymbols.getInstance(Locale.ENGLISH));
/**
* Convert an R expression in a Js expression WARNING: many R syntaxes are not supported yet
*
* @param e - the R expression
* @return the js script expression
*/
private String convertRtoJs(String e) {
String R = null;
if (debug_js) R =e;
e = removeCommentedLines(e);
// remove ; at end of lines. We will re-add it later
e = e.replaceAll(";+ *\\n", "\n");
// non-regexp keys in R2js.propto replace
if (R_TO_JS != null) {
for (Object R_key : R_TO_JS.keySet()) {
String var = Pattern.quote(R_key.toString());
String regexp = AW +var + (R_key.toString().endsWith("(")?")":Az);
Matcher m = Pattern.compile(regexp).matcher(e);
while (m.find()) {
String val = R_TO_JS.getProperty(R_key.toString());
e = e.replace(m.group(), m.group().replace(R_key.toString(), val));
}
}
}
//1E-8 -> 1*10^-8
//e = e.replaceAll("(\\d|\\d\\.)[eE]+([+-])*(\\d)", "$1*10^$2$3");
Matcher m = Pattern.compile("(\\d|\\d\\.)+[eE]+([+-])*(\\d*)").matcher(e);
while (m.find()) {
e = e.replace(m.group(), formatter.format(Double.parseDouble(m.group()))); // direct eval within java
}
// Get all expression in quote and replace them by variables to not
// modify them in this function
quotesList = replaceQuotesByVariables(e, 1);
// Get the expression with replaced quotes (it's the first element of
// the returned list)
e = quotesList.get(0);
//change variable names containing "." by "__", but avoid file names (ending with ')
e = e.replaceAll("([a-zA-Z]+)\\.([a-zA-Z]+)", "$1__$2");
// Replace Math functions
for (String f : MATH_FUN_JS) {
e = e.replaceAll("(\\b)" + f + "\\(", "$1 math." + f + "(");
}
// Replace Math constants
for (String c : MATH_CONST_JS) {
e = e.replaceAll("(\\b)" + c + "(\\b)", "$1 math." + c.toUpperCase() + "$2");
}
// Replace t(x) by math.transpose(x)
e = e.replaceAll("(^|[^a-zA-Z\\d:])t\\(", "$1math.transpose(");
// Replace determinant(x) by math.det(x)
e = e.replaceAll("(^|[^a-zA-Z\\d:])determinant\\(", "$1math.det(");
// Replace solve(A,B) by math.lusolve(A,B)
e = e.replaceAll("(^|[^a-zA-Z\\d:])solve\\(", "$1math.lusolve(");
// Replace dim(A) by r.dim(A)
e = e.replaceAll("(^|[^a-zA-Z\\d\\.:])dim\\(", "$1R.dim(");
// replace '->' by '='
e = e.replaceAll("<<-", "=");
e = e.replaceAll("<-", "=");
// replace "+-" by "-"
e = e.replaceAll("\\+ *-", "-");
// replace 'f = function(x)' by 'function f(x)'
//e = e.replaceAll("([\\w\\-]+) *= *function[(]([\\w\\-[^)]]*)[)]", "function $1($2)");
/*Matcher matcherFunction = Pattern.compile("([\\w\\-]+) *= *function[(]([\\w\\-[^)]]*)[)](.*)").matcher(e);
if (matcherFunction.find()) {
matcherFunction.reset();
StringBuffer sb = new StringBuffer("");
while (matcherFunction.find()) {
StringBuilder f = new StringBuilder();
f.append("function ");
String name = matcherFunction.group(1);
f.append(name);
f.append("(");
f.append(matcherFunction.group(2));
f.append(")");
f.append(matcherFunction.group(3));
functionsSet.add(name);
matcherFunction.appendReplacement(sb, f.toString());
}
matcherFunction.appendTail(sb);
e = sb.toString();
} */
// replace the for expression
e = e.replaceAll("[(]([^=\\s]+)\\s*in\\s*([\\w\\-]+)\\s*:\\s*([[\\w\\-][.][)][(]]+)[)]\\s*",
"($1=$2; $1<=$3; $1++) ");
// Add '{}' between the 'if' and the 'else'
//e = e.replaceAll("if( *[(][^)]*[)])(.[^}]*)else(.*)", "if$1{$2} else{$3}");
e = e.replaceAll("\\bif \\(", "if(");
e = addIfElseBrackets(e);
// Add "{" and "}" if the function doesn't have them
e = e.replaceAll("function([.[^)]]*[)]) *([a-zA-Z0-9].*)$", "function$1 {$2}");
// Add return statement in function if there is no return yet
// FIXME: multiple imbricated functions are not supported for the moment
e = e.replaceAll("function([.[^)]]*[)]) *[{]\\s*(((?!return|function).)*)\\s*[}];*$", "function$1 {return $2}");
// e = e.replaceAll("function([.[^)]]*[)])
// *[{](((?!return|function).)*)[}] *;", "function$1 {return $2};");
// e = e.replaceAll("function([.[^)]]*[)])
// *[{](((?!return|function).)*)[}] *;", "function$1 {return $2};");
// *[{](((?!return|function).)*)[}] *\n", "function$1 {return $2}\n");
// replace operator '**' by '^'
e = e.replaceAll("\\*\\*", "\\^");
// Replace array indexing
e = replaceIndexesSet(e);
e = replaceIndexes(e);
// replace the array in R defined by c(1, 2, ...) by array in js [1, 2, ...]
// FIXME: this will not work with c(a(2), b) because of parenthesis (maybe treat c like other functions?)
e = e.replaceAll("([^A-Za-z0-9]|^)c[(]([.[^):]]*)[)]", "$1[$2]");
e = e.replaceAll("([^A-Za-z0-9]|^)c[(]([.[^)]]*)[)]", "$1$2");
// replace "for (x in array) {...}" by: "var arrayLength = array.length;
// for(var i = 0; i < arrayLength; i++) { x = array[i]; ...}"
// We can't used the "for (x of array)" expression in javascript because
// it's not supported by Java8 and his javascript evaluator
e = e.replaceAll("for *[(]([\\w\\-]+) +in +([\\w\\-]+)[)] *[{]",
"var $2Length = R.dim($2)[0]; for(var i = 0; i < $2Length; i++) {$1 = $2[i]; ");
// Replace "TRUE" by "true" and "FALSE" by "false"
// TODO: Check that 'TRUE' is not inside a variable ex: myTRUEvariable
e = e.replaceAll("TRUE", "true");
e = e.replaceAll("FALSE", "false");
// TODO: Check that 'NULL' is not inside a variable ex: myNULLvariable
e = e.replaceAll("NULL", "null");
// Replace '++' operator by a=a+1
e = e.replaceAll("([^ ]+)\\+\\+", "$1=$1+1");
// Replace '+=' operator by a=a+x
e = e.replaceAll("([^ ]+) *\\+\\=", "$1=$1+");
// FIXME this doesn't support += ...
// Replace operators (+, -, *, /, ...)
e = e.replaceAll("\\=\\=","ê");
e = e.replaceAll("\\<\\=","î");
e = e.replaceAll("\\>\\=","ŝ");
e = e.replaceAll("\\|\\|","ô");
e = e.replaceAll("\\&\\&","â"); // to avoid replacing 'tolerance &' by two _and
e = replaceOperators(e);
// Default parameter ("function(arg = defaultValue) {...}") is defined after the version ES6 of javascript
// Java8 uses a previous version of javascript so we have to transform this expression in:
// function(arg) { arg = typeof arg !== 'undefined' ? arg :
// defaultValue; ...}
Matcher matcher = Pattern.compile("(.*function)[(](.*=.*)[)] *[{]").matcher(e);
if (matcher.find()) {
matcher.reset();
StringBuffer sb = new StringBuffer("");
while (matcher.find()) {
sb.append(matcher.group(1));
String functionCorrected = replaceFunctionDefaultArguments(matcher.group(2));
matcher.appendReplacement(sb, functionCorrected);
}
matcher.appendTail(sb);
e = sb.toString();
}
// replace matrix expression
e = createMatrix(e);
// replace array expression
e = createArray(e);
// replace write csv expression
e = createWriteCSV(e);
// replace data.frame expression
e = createDataFrame(e);
// replace list expression
e = e.replace("list()","{}");
e = createList(e);
// format paste function (with sep & collapse args)
e = createPaste(e);
e = createPaste0(e);
// replace ls fct expression
e = createLs(e);
// replace save fct expression
e = createSaveFunction(e);
// replace load fct expression
e = createLoadFunction(e);
// replace length fct expression
e = createLengthFunction(e);
// replace file.exist fct expression
e = createFileExistsFunction(e);
e = createExistsFunction(e);
e = createStopIfNotFunction(e);
//e = createPredefinedFunction(e);
// change for (x in X) {...} by for (x in R._in(X)) {...}, because in R in returns values for arrays (and keys for maps)
e = e.replaceAll(" in ([^\\{]+)\\{", " in R._in($1){");
// force regular 'if' to throw error when arg is null
e = e.replaceAll("if *\\(([^\\{\\n]+)\\)\\s*(\\{|(return))", "if (R._if($1)) $2");
// replace line return (\n) by ";" if there is a "=" or a "return" in the line
e = e.replaceAll("return(.*)\n", "return$1 ;\n");
//e = e.replaceAll("=(.*)\n", "=$1 ;\n");
e = e.replaceAll("(.[^\\n+-=/\\*]+)\n", "$1 ;\n");
// Remove '+' at begining
e = e.replaceAll("^ *\\+", "");
// Remove unused ';' (after a bracket for instance)
e = e.replaceAll("\\{ *;", "\\{");
//e = e.replaceAll("\\} *;", "\\}"); No! otherwise, will replace 'a={}; \n b=2' by 'a={} \n b=2'
e = e.replaceAll("\\[ *;", "\\[");
e = e.replaceAll("\\( *;", "\\(");
e = e.replaceAll("; *;", ";");
e = e.replaceAll("(\\W*)is__array\\(", "$1Array.isArray(");
e = e.replaceAll("(\\W*)ncol\\(", "$1R.ncol(");
e = e.replaceAll("(\\W*)nrow\\(", "$1R.nrow(");
e = e.replaceAll("(\\W*)names\\(((\\w|\\.)+)\\)\\s*=\\s*", "$1 $2.names = "); // names(X) = "abc"
e = e.replaceAll("(\\W*)names\\(", "$1R.names(");
e = e.replaceAll("(\\W*)length\\(", "$1R.length(");
e = e.replaceAll("(\\W*)dim\\(", "$1R.dim(");
e = e.replaceAll("(\\W*)rep\\(", "$1R.rep(");
e = e.replaceAll("(\\W*)which\\(", "$1R.which(");
e = e.replaceAll("(\\W*)whichMin\\(", "$1R.whichMin(");
e = e.replaceAll("(\\W*)whichMax\\(", "$1R.whichMax(");
e = e.replaceAll("(\\W*)_print\\(", "$1R._print(");
e = e.replaceAll("(\\W*)getwd\\(", "$1R.getwd(");
e = e.replaceAll("(\\W*)setwd\\(", "$1R.setwd(");
e = e.replaceAll("(\\W*)SysSleep\\(", "$1R.SysSleep(");
e = e.replaceAll("(\\W*)isFunction\\(", "$1R.isFunction(");
e = e.replaceAll("(\\W*)isNull\\(", "$1R.isNull(");
e = e.replaceAll("(\\W*)apply\\(", "$1R.apply(");
e = e.replaceAll("(\\W*)rbind\\(", "$1R.rbind(");
e = e.replaceAll("(\\W*)cbind\\(", "$1R.cbind(");
e = e.replaceAll("(\\W*)Rpaste\\(", "$1R.paste(");
e = e.replaceAll("(\\W*)Rpaste0\\(", "$1R.paste0(");
e = e.replaceAll("(\\W*)asNumeric\\(", "$1R.asNumeric(");
e = e.replaceAll("(\\W*)asInteger\\(", "$1R.asInteger(");
e = e.replaceAll("R\\.R\\.", "R.");
e = e.replaceAll("(\\W*)runif\\(", "$1rand.runif(");
e = e.replaceAll("(\\W*)rnorm\\(", "$1rand.rnorm(");
e = e.replaceAll("(\\W*)rpois\\(", "$1rand.rpois(");
e = e.replaceAll("(\\W*)rcauchy\\(", "$1rand.rcauchy(");
e = e.replaceAll("(\\W*)rchisq\\(", "$1rand.rchisq(");
e = e.replaceAll("rand\\.rand\\.", "rand.");
// Replace function() {return if(condition){a} else {b}} by function() {if(condition{return a} else {return b}}
e = replaceReturnIf(e);
try {
e = convertFunction(e);
} catch (ScriptException ex) {
Logger.getLogger(R2jsSession.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
// Store global variables in the object containing all global variables
storeGlobalVariables(e);
e = replaceArgsNames(e, "__");
// Replace variables by variableStorageObject.variable
e = replaceVariables(e, variablesSet, THIS_ENVIRONMENT + ".");
// Finally replace "quotes variables" by their expressions associated
e = replaceNameByQuotes(quotesList, e, false);
// Replace '$' accessor of data.frame by a '.'
e = e.replaceAll("\\$" + THIS_ENVIRONMENT + "\\.", "\\$"); // Remove the JS variable if there is a '$' before
e = e.replaceAll("\\$", ".");
// R Comments
e = e.replaceAll("#", "//");
//e = e.replaceAll("^(.*)#(.*)$", "$1/*$2*/");
if (debug_js) {
String[] lines_R = R.split("\n");
String[] lines_JS = e.split("\n");
int w = maxLength(lines_R);
System.err.println("------------------------------------------------------------------------------------");
for (int i = 0; i < Math.max(lines_R.length, lines_JS.length); i++) {
System.err.println(
rightPad((lines_R.length > i ? lines_R[i] : ""), w)
+ " | " + rightPad((i +1)+ "", 3) + " | "
+ (lines_JS.length > i ? lines_JS[i] : ""));
}
System.err.println("------------------------------------------------------------------------------------");
}
return e;
}
private String convertFunction(String expr) throws ScriptException {
Pattern indexPattern = Pattern.compile("(^|[^\\.\\w])((?!function|if|for|while|switch|return)\\w+)[(]");
Matcher indexMatcher = indexPattern.matcher(expr);
if (indexMatcher.find()) {
indexMatcher.reset();
while (indexMatcher.find()) {
String fctName = indexMatcher.group(2);
// Ignore if the functions is already defined
if(!this.variablesSet.contains(fctName)) {
// If the function is not defined yet in js environment
if(!this.asLogical(this.js.eval("typeof "+ fctName +" !== 'undefined'"))) {
this.variablesSet.add(fctName);
}
}
}
} else {
return expr;
}
return expr;
}
int maxLength(String... s) {
int ml = 0;
for (String l : s) {
ml = Math.max(ml, l.length());
}
return ml;
}
String rightPad(String s, int pad) {
if (s == null) {
s = "";
}
if (s.length() > pad) {
return s.substring(0, pad);
}
StringBuffer sb = new StringBuffer(pad);
for (int i = 0; i < pad - s.length(); i++) {
sb.append(" ");
}
return s + sb.toString();
}
/**
* Remove commented lines
*
* @param expr
* @return
*/
private String removeCommentedLines(String expr) {
StringBuilder sb = new StringBuilder();
String lines[] = expr.split("\\r?\\n");
for(String line : lines) {
if(!line.trim().startsWith("#")) {
sb.append(line);
sb.append("\n");
}
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* Add prefix before arguments names of the function to not replace
* them after by "global variables"
*
* Ex:
* In this example we add the prefix "__" before var1 to not mix up with the __this__.var1 variable
* var1 <- 1 : __this__.var1 = 1
* f1 <- function(var1) { var1 + 1} : __this__.f1 = function(__var1) {return math.add(__var1 , 1)}
*
* @param expr - the expression to replace
* @param prefix - the prefix to add before arguments of the function
* @return
*/
private String replaceArgsNames(String expr, String prefix) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "function");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
Collection arguments = Arrays.stream(argumentsMap.get("default").split(",")).map(String::trim).collect(Collectors.toList());
int ifStartBracketIndex = result.substring(endIndex).indexOf("{") + endIndex;
int fctCloseBracketIndex = getNextExpressionLastIndex(result, ifStartBracketIndex+1, "}")+1;
String function = result.substring(startIndex, fctCloseBracketIndex+1);
// Prefix function's arguments by "__"
String replacedFunction = replaceVariables(function, arguments, prefix);
replacedFunction = replacedFunction.replaceAll("(\\b)function(\\b)", "$1_function$2");
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(replacedFunction);
sb.append(result.substring(fctCloseBracketIndex + 1));
result = sb.toString();
// Search the next "function" in the expression
rFunctionArgumentsDTO = getFunctionArguments(result, "function");
}
result = result.replaceAll("(\\b)_function(\\b)", "$1function$2");
// Now replace also in calls to functions: f(a=2) -> f(__a=2)
for (String f : variablesSet) {
Pattern fPattern = Pattern.compile("\\b("+f+")\\(([^\\)]*)\\)");
Matcher fMatcher = fPattern.matcher(result);
if (fMatcher.find()) {
fMatcher.reset();
StringBuffer result_buf = new StringBuffer();
while (fMatcher.find()) {
String args = " " + fMatcher.group(2) + " ";
//System.err.println("args: "+args);
String[] argsArray = splitString(args, ",");
StringBuffer args_buf = new StringBuffer();
for (int i = 0; i < argsArray.length; i++) {
//System.err.println(" - "+argsArray[i]);
if (argsArray[i].contains("=")) {
String[] kv_arg = argsArray[i].split("=");
argsArray[i] = "__" + kv_arg[0].trim() + " = " + kv_arg[1];
}
args_buf.append(argsArray[i] + (i==(argsArray.length-1)?"":","));
}
fMatcher.appendReplacement(result_buf, (f+"("+args_buf.toString()+")").replace("$", "\\$"));
}
fMatcher.appendTail(result_buf);
result = result_buf.toString();
}
}
return result;
}
/**
* Replace all variables by JS_VARIABLE_STORAGE_OBJECT.variable to access
* variable define in this JS object
*
* WARNING: If two variables has the same name (because one is global and
* the other is local for example), there will be an error.
*
* @param expr - the expression to replace
* @param variables - the variables to replace
* @return the expression with replaced variables
*/
private String replaceVariables(String expr, Iterable variables, String prefix) {
String result = expr;
for (String variable : variables) {
if (variable.length() > 0) {
//result = result.replaceAll("(\\b)^((?!" + JS_VARIABLE_STORAGE_OBJECT + "\\.).)*(\\b)(" + variable + ")(\\b)", JS_VARIABLE_STORAGE_OBJECT + "." + variable);
result = result.replaceAll("\\b(? replaceQuotesByVariables(String expr, int startIndex) {
Pattern squotesPattern = Pattern.compile("(\'[^\']*\')");
Matcher squotesMatcher = squotesPattern.matcher(expr);
List quotesList = new ArrayList<>();
quotesList.add(expr);
StringBuffer sb = new StringBuffer();
int cmp = startIndex;
while (squotesMatcher.find()) {
quotesList.add(squotesMatcher.group(1));
squotesMatcher.appendReplacement(sb, "QUOTE_EXPRESSION_" + cmp+"_"); // need to finish with _ otherwise _1 will replace also _10
cmp++;
}
squotesMatcher.appendTail(sb);
Pattern quotesPattern = Pattern.compile("(\"[^\"]*\")");
Matcher quotesMatcher = quotesPattern.matcher(sb.toString());
StringBuffer sb2 = new StringBuffer();
while (quotesMatcher.find()) {
quotesList.add(quotesMatcher.group(1));
quotesMatcher.appendReplacement(sb2, "QUOTE_EXPRESSION_" + cmp+"_"); // need to finish with _ otherwise _1 will replace also _10
cmp++;
}
quotesMatcher.appendTail(sb2);
quotesList.set(0, sb2.toString());
return quotesList;
}
/**
* Replace variables: QUOTE_EXPRESSION_ID by their expression in quotesList
*
* @param quotesList
* - a list containing all quotes expression in the same order
* than the ID of variables. (WARNING: The first index of the
* list is not used)
* @param expr
* - expression containing variables to replace by quotes
* expressions
* @return the expression with variables replaced by quotes expressions
*/
private static String replaceNameByQuotes(List quotesList, String expr, boolean removeRoundingQuotes) {
for (int i = 1; i < quotesList.size(); i++) {
String quote = quotesList.get(i).trim();
if(removeRoundingQuotes) {
quote = quote.substring(1, quote.length()-1);
}
quote = quote.replace("\\", "\\\\\\\\"); // Add more backslash to pass java and js
expr = expr.replaceAll("QUOTE_EXPRESSION_" + i+"_", quote);
}
return expr;
}
/**
* LS function
*
* @param e - the expression containing the function to replace
* @return the expression with replaced function
*/
private String createLs(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "ls");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
// Pattern to match
String pattern = argumentsMap.get("pattern");
// Build the mathjs expression to generate random uniform
// distribution
StringBuilder unifRandomSb = new StringBuilder();
// If there is a regex pattern argument
if (pattern != null) {
unifRandomSb.append("R.removeMatching(Object.keys(");
unifRandomSb.append(THIS_ENVIRONMENT);
unifRandomSb.append("), new RegExp(");
unifRandomSb.append(pattern);
unifRandomSb.append("))");
} else {
unifRandomSb.append("Object.keys(");
unifRandomSb.append(THIS_ENVIRONMENT);
unifRandomSb.append(")");
}
// Replace the R matrix expression by the current matrix js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(unifRandomSb.toString());
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "ls" fct
rFunctionArgumentsDTO = getFunctionArguments(result, "ls");
}
return result;
}
@Override
public String[] ls(boolean all) {
List variablesandfunctions = new LinkedList<>();
variablesandfunctions.addAll(variablesSet);
variablesandfunctions.addAll(functionsSet);
variablesandfunctions.removeAll(Arrays.asList(DISABLED_FUNCTIONS));
// replace by Obejct.keys() ?
if (all)
return variablesandfunctions.toArray(new String[]{});
else {
List notall = new LinkedList<>();
for (String v : variablesandfunctions) {
if (!v.startsWith("_")) notall.add(v);
}
return notall.toArray(new String[]{});
}
}
/**
* Split an array with the separator only if the separator string is not in parenthesis or bracket
* @param expr - the expression containing the array to replace
* @param sep - the separator between values in the array
* @return the split String array
*/
private static String[] splitString(String expr, String sep) {
List splitList = new ArrayList<>();
int sepIndex = -1;
int exprLength = expr.length();
while (sepIndex < exprLength) {
int nextIndex = getNextExpressionLastIndex(expr, sepIndex, sep) + 1;
String argumentName = expr.substring(sepIndex+1, nextIndex).trim();
splitList.add(argumentName);
sepIndex = nextIndex;
}
return splitList.toArray(new String[0]);
}
static String arrayIfNeeded(String[] l) {
if (l == null) {
return null;
}
StringBuffer arrayif = new StringBuffer(l[0]);
boolean truearray = false;
if (l.length > 0) {
for (int i = 1; i < l.length; i++) {
if (l[i].trim().length() > 0) {
truearray = true;
arrayif.append(" , " + l[i]);
}
}
}
if (truearray) {
return " " + arrayif.toString() + " ";
} else {
return arrayif.toString();
}
}
// accept:
//X[i]
//X[[i]]
//X[,i]
//f(X)[i]
// reject:
//f(X[i]
//X[Y[i]]
//X[[Y[i]]]
private static String index_pattern = "([\\w|\\$|\\.]+(\\([\\w|\\$|\\=|\\,|\\-|\\(|\\)|\\.]*\\))*[\\w|\\$|\\.]*)\\[+(.[^\\]]*)\\]+";
// to get only one '[': "([\\w|\\$|\\.]+(\\([\\w|\\$|\\=|\\,|\\-|\\(|\\)|\\.]+\\))*[\\w|\\$|\\.]*)\\[([.[^\\[\\]]]*)\\]"
/**
* Replace indexes by mathjs indexes
*
* @param expr - the expression containing indexes to replace
* @return the expression with replaced indexes
*/
public static String replaceIndexes(String expr) {
Matcher intricated = Pattern.compile(".*(\\[+)(.[^\\]]+)(\\[).*").matcher(expr);
if (intricated.find()) {
intricated.reset();
List found = new LinkedList<>();
while(intricated.find()){
found.add(intricated.group(1)+intricated.group(2)+intricated.group(3));
}
throw new UnsupportedOperationException("Intricated indexes 'abc[def[i]]' not supported at:"+String.join("\n", found));
}
Pattern indexPattern = Pattern.compile(index_pattern);
Matcher indexMatcher = indexPattern.matcher(expr);
if (indexMatcher.find()) {
indexMatcher.reset();
StringBuffer sb = new StringBuffer("");
while (indexMatcher.find()) {
String arrayName = indexMatcher.group(1);
String indexes =" " + indexMatcher.group(3) + " ";
String[] indexesArray = splitString(indexes, ",");
for (int i = 0; i < indexesArray.length; i++) {
if (indexesArray[i].trim().equals("")) { // If the index is empty, we create an range array to select all the line
int dim = i;
indexesArray[i] = "math.range(1, 1+math.subset(dim(" + arrayName + "), math.index(" + dim + ")))"; //range starting from 1, because R.r_index will apply -1
}
}
StringBuilder result = new StringBuilder();
result.append("math.squeeze(math.subset(");
result.append(arrayName);
result.append(", R._index(");
result.append(arrayIfNeeded(indexesArray));
result.append(")))");
indexMatcher.appendReplacement(sb, result.toString().replace("$", "\\$"));
}
indexMatcher.appendTail(sb);
return sb.toString();
} else {
return expr;
}
}
public static String replaceIndexesSet(String expr) {
Matcher intricated = Pattern.compile(".*(\\[+)(.[^\\]]+)(\\[).*").matcher(expr);
if (intricated.find()) {
intricated.reset();
List found = new LinkedList<>();
while(intricated.find()){
found.add(intricated.group(1)+intricated.group(2)+intricated.group(3));
}
throw new UnsupportedOperationException("Intricated indexes 'abc[def[i]]' not supported at:"+String.join("\n", found));
}
Pattern indexPattern = Pattern.compile(index_pattern+"\\s*[\\=]{1}(.*)");
Matcher indexMatcher = indexPattern.matcher(expr);
if (indexMatcher.find()) {
indexMatcher.reset();
StringBuffer sb = new StringBuffer("");
while (indexMatcher.find()) {
String arrayName = indexMatcher.group(1);
String indexes = " " + indexMatcher.group(3) + " ";
String toset = " " + indexMatcher.group(4) + " ";
String[] indexesArray = splitString(indexes, ",");
for (int i = 0; i < indexesArray.length; i++) {
// If the index is empty, we create an range array to select all the line
if (indexesArray[i].trim().equals("")) {
int dim = i;
indexesArray[i] = "math.range(1, 1+math.subset(dim(" + arrayName + "), math.index(" + dim + ")))"; //range starting from 1, because R.r_index will apply -1
}
}
StringBuilder result = new StringBuilder();
result.append(arrayName + " = math.subset(");
result.append(arrayName);
result.append(", R._index(");
result.append(arrayIfNeeded(indexesArray));
result.append(")," + toset + ")");
indexMatcher.appendReplacement(sb, result.toString().replace("$", "\\$"));
}
indexMatcher.appendTail(sb);
return sb.toString();
} else {
return expr;
}
}
/**
* This function add brackets in if/else expression.
* In nashorn (Java8_161 - ECMA5) the if without '{' work but not the if else without '{'
*
* Example: Before: "if(a>1) 1 else 2"
* After : "if(a>1) {1} else {2}"
* @param expr
* @return the transformed expression
*/
private static String addIfElseBrackets(String expr) {
final String ifReplacementString = "__IF__";
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "if", true);
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
// Data to write
String ifArg = argumentsMap.get("default");
StringBuilder ifSb = new StringBuilder();
ifSb.append(ifReplacementString);
ifSb.append("(");
ifSb.append(ifArg);
ifSb.append(")");
if(!result.substring(endIndex+1).trim().startsWith("{") && expr.indexOf("else", endIndex) >= 0) {
ifSb.append("{");
int elseIndex = expr.indexOf("else", endIndex);
if(elseIndex >= 0) {
ifSb.append(result.substring(endIndex+1, elseIndex));
ifSb.append("} else {");
ifSb.append(result.substring(elseIndex+5));
} else {
ifSb.append(result.substring(endIndex+1, result.length()));
}
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(ifSb.toString());
sb.append("}");
result = sb.toString();
} else {
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(ifSb.toString());
sb.append(result.substring(endIndex + 1));
result = sb.toString();
}
// Search the next "if"
rFunctionArgumentsDTO = getFunctionArguments(result, "if", true);
}
result = result.replaceAll(ifReplacementString, "if");
return result;
}
/**
* Convert the R expression or write csv: write.csv(data, file) to js
* expression: r.write(file, data)
*
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private static String createWriteCSV(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "write__csv");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
// Data to write
String data = argumentsMap.get("data");
// File
String file = argumentsMap.get("file");
// Build the mathjs expression to generate random uniform
// distribution
StringBuilder unifRandomSb = new StringBuilder();
unifRandomSb.append("R.write(");
unifRandomSb.append(file);
unifRandomSb.append(", ");
unifRandomSb.append(data.trim());
unifRandomSb.append(".toString())");
// Replace the R matrix expression by the current matrix js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(unifRandomSb.toString());
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "array"
rFunctionArgumentsDTO = getFunctionArguments(result, "write__csv");
}
return result;
}
/**
* Convert the R expression of data.frame to js object.
*
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private String createDataFrame(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "data__frame");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
StringBuilder dataFrameSb = new StringBuilder();
dataFrameSb.append("R.createMathObject({");
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
for (Map.Entry entry : argumentsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(key.equals(value))
dataFrameSb.append("'");
dataFrameSb.append(key);
if(key.equals(value))
dataFrameSb.append("'");
dataFrameSb.append(":");
dataFrameSb.append(value);
dataFrameSb.append(",");
}
dataFrameSb.replace(dataFrameSb.length()-1, dataFrameSb.length(), "})");
// Replace the R matrix expression by the current matrix js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(dataFrameSb.toString());
sb.append(result.substring(endIndex + 1));
result = sb.toString();
List newQuoteList = replaceQuotesByVariables(result,quotesList.size());
result = newQuoteList.get(0);
for(int i=1; i argumentsMap = rFunctionArgumentsDTO.getGroups();
for (Map.Entry entry : argumentsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(key.equals(value))
dataFrameSb.append("'");
dataFrameSb.append(key);
if(key.equals(value))
dataFrameSb.append("'");
dataFrameSb.append(":");
dataFrameSb.append(value);
dataFrameSb.append(",");
}
dataFrameSb.replace(dataFrameSb.length()-1, dataFrameSb.length(), "})");
// Replace the R matrix expression by the current matrix js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(dataFrameSb.toString());
sb.append(result.substring(endIndex + 1));
result = sb.toString();
List newQuoteList = replaceQuotesByVariables(result,quotesList.size());
result = newQuoteList.get(0);
for(int i=1; i argumentsMap = rFunctionArgumentsDTO.getGroups();
String data = argumentsMap.get("data");
String nrow = argumentsMap.get("nrow");
String ncol = argumentsMap.get("ncol");
String byrow = argumentsMap.get("byrow");
// Build the mathjs expression to create an array/matrix
StringBuilder arraySb = new StringBuilder();
if (nrow == null && ncol == null) {
// We just create an array nrow and ncol are null
nrow = "math.prod(dim(" + data + "))";
ncol = "1";
} else if (nrow != null) {
if (ncol == null) {
// compute the number of columns
ncol = "math.ceil(math.dotDivide(math.prod(dim(" + data + ")), " + nrow + "))";
}
} else if (ncol != null) {
// compute the number of rows
nrow = "math.ceil(math.dotDivide(math.prod(dim(" + data + ")), " + ncol + "))";
}
// Create the array containing the dimension of the result matrix
StringBuilder stringDimArraybuilder = new StringBuilder();
stringDimArraybuilder.append("dim = [");
stringDimArraybuilder.append(nrow);
stringDimArraybuilder.append(", ");
stringDimArraybuilder.append(ncol);
stringDimArraybuilder.append("]");
String stringDimArray = stringDimArraybuilder.toString();
if (byrow == null || byrow.equals("false")) {
arraySb.append("math.transpose(math.reshape(R.expendArray(");
arraySb.append(data);
arraySb.append(", math.prod(");
arraySb.append(stringDimArray);
arraySb.append(")), ");
arraySb.append(stringDimArray);
arraySb.append(".reverse()))");
} else {
arraySb.append("math.reshape(R.expendArray(");
arraySb.append(data);
arraySb.append(", math.prod(");
arraySb.append(stringDimArray);
arraySb.append(")), ");
arraySb.append(stringDimArray);
arraySb.append(")");
}
// Replace the R matrix expression by the current matrix js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(arraySb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "array"
rFunctionArgumentsDTO = getFunctionArguments(result, "matrix");
}
return result;
}
/**
* Convert the R expression: array(c(1, 2, 3),dim = c(3,3,2)) to js array
*
* FIXME: this function doesn't work with more than 2 dimensions FIXME: this
* function doesn't work recursively ( "array(array(...))") FIXME: this
* function doesn't work with variables !!!
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private static String createArray(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "array");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
// Array containing data
String stringArray = argumentsMap.get("data");
// Array containing dimension
String stringDimArray = argumentsMap.get("dim");
// Build the mathjs expression to create an array/matrix
StringBuilder arraySb = new StringBuilder();
// If the field 'dim' is not null
if (stringDimArray != null) {
arraySb.append("math.transpose(math.reshape(R.expendArray(");
arraySb.append(stringArray);
arraySb.append(", math.prod(");
arraySb.append(stringDimArray);
arraySb.append(")), ");
arraySb.append(stringDimArray);
arraySb.append(".reverse()))");
} else {
// Else we flat the matrix
arraySb.append("math.reshape(");
arraySb.append(stringArray);
arraySb.append(", math.multiply(math.ones(1),math.prod(dim(");
arraySb.append(stringArray);
arraySb.append("))))");
}
// Replace the R matrix expression by the current matrix js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(arraySb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "array"
rFunctionArgumentsDTO = getFunctionArguments(result, "array");
}
return result;
}
private String createPaste(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "paste");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
StringBuilder dataFrameSb = new StringBuilder();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String sep = argumentsMap.getOrDefault("sep", "' '");
String collapse = argumentsMap.getOrDefault("collapse", "';'");
dataFrameSb.append("Rpaste(" + sep + "," + collapse + ",");
//startIndex = startIndex+sep.length()+1+collapse.length()+1; //to move after sep & collapse args
for (Map.Entry entry : argumentsMap.entrySet()) {
String key = entry.getKey();
if (!key.equals("sep") && !key.equals("collapse")) {
String value = entry.getValue();
dataFrameSb.append(value);
dataFrameSb.append(",");
}
}
dataFrameSb.replace(dataFrameSb.length() - 1, dataFrameSb.length(), ")");
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(dataFrameSb.toString());
sb.append(result.substring(endIndex + 1));
result = sb.toString();
List newQuoteList = replaceQuotesByVariables(result, quotesList.size());
result = newQuoteList.get(0);
for (int i = 1; i < newQuoteList.size(); i++) {
quotesList.add(newQuoteList.get(i));
}
// Search the next "paste"
rFunctionArgumentsDTO = getFunctionArguments(result, "paste");
}
return result;
}
private String createPaste0(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "paste0");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
StringBuilder dataFrameSb = new StringBuilder();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String collapse = argumentsMap.getOrDefault("collapse", "';'");
dataFrameSb.append("Rpaste0(" + collapse + ",");
//startIndex = startIndex+sep.length()+1+collapse.length()+1; //to move after sep & collapse args
for (Map.Entry entry : argumentsMap.entrySet()) {
String key = entry.getKey();
if (!key.equals("collapse")) {
String value = entry.getValue();
dataFrameSb.append(value);
dataFrameSb.append(",");
}
}
dataFrameSb.replace(dataFrameSb.length() - 1, dataFrameSb.length(), ")");
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(dataFrameSb.toString());
sb.append(result.substring(endIndex + 1));
result = sb.toString();
List newQuoteList = replaceQuotesByVariables(result, quotesList.size());
result = newQuoteList.get(0);
for (int i = 1; i < newQuoteList.size(); i++) {
quotesList.add(newQuoteList.get(i));
}
// Search the next "paste"
rFunctionArgumentsDTO = getFunctionArguments(result, "paste0");
}
return result;
}
/**
* This function replaces the R function save by JS equivalent
* It writes in file the variable with its value sperated by ':' in the file (example: "variable:value")
* WARNING the function works only if the variable to save is between quotes
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private String createSaveFunction(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "save");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String listString = argumentsMap.get("list");
String fileString = argumentsMap.get("file");
String listStringUnquotted = listString.replace("\'", "");
// Build the mathjs expression to create an array/matrix
StringBuilder saveSb = new StringBuilder();
saveSb.append("R.write(");
saveSb.append(fileString);
saveSb.append(", ");
saveSb.append("R.createJsonString(");
saveSb.append(listStringUnquotted);
saveSb.append(", ");
saveSb.append(THIS_ENVIRONMENT);
saveSb.append("))");
// Replace the R matrix expression by the current matrix js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(saveSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "save" in the expression
rFunctionArgumentsDTO = getFunctionArguments(result, "save");
}
return result;
}
/**
* Load variables in a json
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private String createLoadFunction(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "load");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String fileString = argumentsMap.get("file");
// Add all loaded variables to the java list of variables
try {
String readVariablesExpr = replaceNameByQuotes(quotesList,"R.readJsonVariables(" + fileString + ")", false);
String[] loadedVariables = (String[])cast(js.eval(readVariablesExpr));
addGlobalVariables(loadedVariables);
} catch (ScriptException ex) {
Logger.getLogger(R2jsSession.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
// Build the mathjs expression to create load data
StringBuilder loadSb = new StringBuilder();
loadSb.append("R.loadJson(");
loadSb.append(fileString);
loadSb.append(", ");
loadSb.append(THIS_ENVIRONMENT);
loadSb.append(")");
// TODO add loaded function as global variables: storeGlobalVariables(String expr)
// Replace the R load expression by the current load js
// expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(loadSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "load"
rFunctionArgumentsDTO = getFunctionArguments(result, "load");
}
return result;
}
/**
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private static String createLengthFunction(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "length");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String values = argumentsMap.get("default");
// Build the mathjs expression
StringBuilder rbindSb = new StringBuilder();
rbindSb.append("math.squeeze(math.subset(dim(");
rbindSb.append(values);
rbindSb.append("), math.index(0)))");
// Replace the R cbind expression by the current js expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(rbindSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "cbind" in the expression
rFunctionArgumentsDTO = getFunctionArguments(result, "length");
}
return result;
}
/**
* Replace 'function() {return if(condition){a} else {b}}' by:
* 'function() {if(condition{return a} else {return b}}'
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private static String replaceReturnIf(String expr) {
String result = expr;
result = result.replaceAll("\\breturn +if *\\(", "returnif(");
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(result, "returnif", true);
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String values = argumentsMap.get("default");
// Build the mathjs expression
StringBuilder ifSb = new StringBuilder();
ifSb.append("if(");
ifSb.append(values);
ifSb.append(") {return ");
int ifStartBracketIndex = result.substring(endIndex).indexOf("{") + endIndex;
int ifCloseBracketIndex = getNextExpressionLastIndex(result, ifStartBracketIndex+1, "}")+1;
ifSb.append(result.substring(ifStartBracketIndex+1, ifCloseBracketIndex+1));
//ifSb.append("}");
if(result.substring(ifCloseBracketIndex+1).trim().startsWith("else")) {
int elseStartBracketIndex = result.substring(ifCloseBracketIndex+1).indexOf("{") + ifCloseBracketIndex+1;
int elseCloseBracketIndex = getNextExpressionLastIndex(result, elseStartBracketIndex + 1, "}")+1;
ifSb.append(" else {return ");
ifSb.append(result.substring(elseStartBracketIndex+1, elseCloseBracketIndex+1));
endIndex = elseCloseBracketIndex;
} else {
endIndex = ifCloseBracketIndex;
}
// Replace the R cbind expression by the current js expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(ifSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "return if" in the expression
result = result.replaceAll("\\breturn +if *\\(", "returnif(");
rFunctionArgumentsDTO = getFunctionArguments(result, "returnif");
}
return result;
}
/**
* This function replaces the R function file.exist in JavaScript
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private static String createFileExistsFunction(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "file__exists");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String values = argumentsMap.get("default");
// Build the mathjs expression
StringBuilder fileExistSb = new StringBuilder();
fileExistSb.append("R.fileExists(");
fileExistSb.append(values);
fileExistSb.append(")");
// Replace the R file.exist expression by the current js expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(fileExistSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "file.exist" in the expression
rFunctionArgumentsDTO = getFunctionArguments(result, "file__exists");
}
return result;
}
/**
* This function replaces the R function exists in JavaScript
* WARNING: arguments('where', 'envir', 'frame', 'mode' and 'inherits') are not supported yet and ignored
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private String createExistsFunction(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "exists");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String variable = argumentsMap.get("default");
// Build the mathjs expression
StringBuilder fileExistSb = new StringBuilder();
fileExistSb.append(THIS_ENVIRONMENT);
fileExistSb.append(".hasOwnProperty(");
fileExistSb.append(variable);
fileExistSb.append(")");
// Replace the R exists expression by the current js expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(fileExistSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "exists" in the expression
rFunctionArgumentsDTO = getFunctionArguments(result, "exists");
}
return result;
}
/**
* This function replace stopifnot(...) by R.stopIfNot('...')
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private static String createStopIfNotFunction(String expr) {
String result = expr;
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "stopifnot");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
String values = argumentsMap.get("default");
// Build the mathjs expression
StringBuilder fileExistSb = new StringBuilder();
fileExistSb.append("R.stopIfNot(");
fileExistSb.append(values);
fileExistSb.append(",'");
fileExistSb.append(values);
fileExistSb.append("')");
// Replace the R file.exist expression by the current js expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(fileExistSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "stopifnot" in the expression
rFunctionArgumentsDTO = getFunctionArguments(result, "stopifnot");
}
return result;
}
/**
* This function replace f = function(x=1, y=2) by
* f = function(x = typeof x != 'undefined' ? x : 1, y = typeof y != 'undefined' ? y : 2)
*
*
* @param expr - the expression containing the function to replace
* @return the expression with replaced function
*/
private static String createPredefinedFunction(String expr) {
String result = expr;
String functionReplacementString = "__FUNCTION_REPLACEMENT";
RFunctionArgumentsDTO rFunctionArgumentsDTO = getFunctionArguments(expr, "function");
while (rFunctionArgumentsDTO != null) {
int startIndex = rFunctionArgumentsDTO.getStartIndex();
int endIndex = rFunctionArgumentsDTO.getStopIndex();
Map argumentsMap = rFunctionArgumentsDTO.getGroups();
// Build the mathjs expression
StringBuilder functionSb = new StringBuilder();
functionSb.append(functionReplacementString);
functionSb.append("(");
for(Map.Entry entry : argumentsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
functionSb.append("(");
functionSb.append(key);
functionSb.append(" = typeof ");
functionSb.append(key);
functionSb.append(" !== 'undefined') ? ");
functionSb.append(key);
functionSb.append(" : ");
functionSb.append(value);
functionSb.append(",");
}
functionSb = functionSb.deleteCharAt(functionSb.length() -1);
functionSb.append(");");
// Replace the R file.exist expression by the current js expression
StringBuilder sb = new StringBuilder();
sb.append(result.substring(0, startIndex));
sb.append(functionSb);
sb.append(result.substring(endIndex + 1));
result = sb.toString();
// Search the next "function" in the expression
rFunctionArgumentsDTO = getFunctionArguments(result, "function");
}
result = result.replaceAll(functionReplacementString, "function");
return result;
}
private static RFunctionArgumentsDTO getFunctionArguments(String expr, String fctName) {
return getFunctionArguments(expr, fctName, false);
}
/**
* Get the beginning, the ending and arguments of a function. This function search for the first occurence
* of "fctName" in the "expr" and return a DTO containing all these informations.
* If an argument field is not informed, a default field will be affected.
*
* @param expr : the expression where we search the function
* @param fctName : the name of the wanted function
* @param ignoreOperators: if true, ignore '==' and return only arguments separated by ','
* @return a DTO containing: start index , end index and arguments of the function found
*/
private static RFunctionArgumentsDTO getFunctionArguments(String expr, String fctName, boolean ignoreOperators) {
// Map containing possible arguments associated to each R functions
Map> argumentNamesByFunctions = new LinkedHashMap<>();
argumentNamesByFunctions.put("array", Arrays.asList("data", "dim", "dimnames"));
argumentNamesByFunctions.put("paste0", new ArrayList());
argumentNamesByFunctions.put("paste", new ArrayList());
argumentNamesByFunctions.put("vector", Arrays.asList("mode", "length"));
argumentNamesByFunctions.put("matrix", Arrays.asList("data", "nrow", "ncol", "byrow", "dimnames"));
argumentNamesByFunctions.put("c", Arrays.asList("data", "dim", "dimnames"));
argumentNamesByFunctions.put("ls", Arrays.asList("name", "pos", "envir", "all.names", "pattern", "sorted"));
argumentNamesByFunctions.put("save", Arrays.asList("list", "file", "ascii", "all.names", "pattern", "sorted"));
argumentNamesByFunctions.put("load", Arrays.asList("file", "envir", "verbose"));
argumentNamesByFunctions.put("write__csv", Arrays.asList("data", "file", "row.names", "col.names", "sep", "na"));
argumentNamesByFunctions.put("data__frame", new ArrayList());
argumentNamesByFunctions.put("list", new ArrayList());
argumentNamesByFunctions.put("length", Arrays.asList("default"));
argumentNamesByFunctions.put("file__exists", Arrays.asList("default"));
argumentNamesByFunctions.put("exists", Arrays.asList("default", "where", "envir", "mode", "frame","inherits"));
argumentNamesByFunctions.put("stopifnot", Arrays.asList("default"));
argumentNamesByFunctions.put("function", Arrays.asList("default"));
argumentNamesByFunctions.put("if", Arrays.asList("default"));
argumentNamesByFunctions.put("returnif", Arrays.asList("default"));
RFunctionArgumentsDTO rFunctionArgumentsDTO = null;
List argumentNamesList = new ArrayList<>(argumentNamesByFunctions.get(fctName));
Map argumentNamesAndValues = new LinkedHashMap<>(); //because we need to keep order of arguments, by default
Pattern pattern = Pattern.compile("(\\b)" + fctName + "\\(");
Matcher matcher = pattern.matcher(expr);
// If an occurrence has been found
if (matcher.find()) {
int startIndex = matcher.start();
int currentIndex = startIndex + fctName.length() + 1;
while (expr.charAt(currentIndex - 1) != ')') {
String operators = ",=";
if(ignoreOperators) {
operators = ",";
}
int argumentEndIndex = getNextExpressionLastIndex(expr, currentIndex - 1, operators);
// Ignore if it is a comparison operator ( '!=', '<=', '>=' or '==')
if (expr.charAt(argumentEndIndex + 1) == '=' && ("!=<>".contains(""+expr.charAt(argumentEndIndex)) || expr.charAt(argumentEndIndex + 2)=='=')) {
argumentEndIndex = getNextExpressionLastIndex(expr, argumentEndIndex+1, operators);
}
String argumentName = null;
String argument = null;
if (expr.charAt(argumentEndIndex + 1) == '=') {
argumentName = expr.substring(currentIndex, argumentEndIndex + 1).trim();
currentIndex = argumentEndIndex + 2;
argumentEndIndex = getNextExpressionLastIndex(expr, currentIndex - 1, operators);
}
if (argumentName == null) {
if(argumentNamesList.size()>0) {
// If argument has no "name", we take the first of the list
argumentName = argumentNamesList.get(0);
// And we remove it from the list unless the name is "default"
if(!argumentName.equals("default"))
argumentNamesList.remove(0);
}
} else {
// Remove the current argument name from the list
boolean removed = argumentNamesList.remove(argumentName);
// if (!removed) {
// Log.Err.println("Unknown argument:" + argumentName + " in function: " + fctName);
// }
}
argument = expr.substring(currentIndex, argumentEndIndex + 1);
// If there is not argument name and the list argumentNamesList is empty, the argumentName is the name of argument
if(argumentName == null) {
argumentName = argument;
}
// If the map already contains the argumentName, we add the new argument after and separated with comma
if(argumentNamesAndValues.containsKey(argumentName)){
argumentNamesAndValues.put(argumentName, argumentNamesAndValues.get(argumentName) + "," + argument);
} else {
argumentNamesAndValues.put(argumentName, argument);
}
currentIndex = argumentEndIndex + 2;
}
rFunctionArgumentsDTO = new RFunctionArgumentsDTO(startIndex, currentIndex - 1, argumentNamesAndValues);
}
return rFunctionArgumentsDTO;
}
/**
* Replace '+' operator by the math.add() operator. To do that we need to
* find what are the expressions to add, they can contains '(' or ')' This
* function start by replacing priority operators '*' and '/' and then
* replace '+' and '-'
*
*
* @param expr - the expression containing operators to replace
* @return the expression with replaced operators
*/
private static String replaceOperators(String expr) {
String previousStoppingCharacters = "=*/^;%+:,><&|ôâêîŝ \n";
String nextStoppingCharacters = "=*+/^%;:,><&|ôâêîŝ \n";
expr = expr.replaceAll("(\\*|/) *-", "$1-");
expr = expr.replaceAll("-", " -");
Map operatorsMap = new HashMap<>();
operatorsMap.put(">", "R._gt");
operatorsMap.put("<", "R._lt");
operatorsMap.put("ŝ", "R._get");
operatorsMap.put("î", "R._let");
operatorsMap.put("ê", "R._eq");
operatorsMap.put("|", "R._or");
operatorsMap.put("ô", "R._oror");
operatorsMap.put("&", "R._and");
operatorsMap.put("â", "R._andand");
operatorsMap.put("+", "math.add");
operatorsMap.put("-", "math.subtract");
operatorsMap.put("*", "math.dotMultiply");
operatorsMap.put("/", "math.dotDivide");
operatorsMap.put("%*%", "math.multiply");
operatorsMap.put("%/%", "math.floor(math.dotDivide");
operatorsMap.put("%%", "math.mod");
operatorsMap.put(":", "R.range");
operatorsMap.put("^", "math.dotPow");
String[] operators = new String[] {"^", "*/%:ê", "+-&|ôâîŝ><" };
// replace '^' first then replace '*','/','%' and ':' and finaly replace '+' and '-'
int priority = 0;
boolean continueReplacing = true;
int i = 0;
while (continueReplacing) {
char currentChar = expr.charAt(i);
// Ignore operators inside quotes
if (currentChar == '\'') {
i++;
currentChar = expr.charAt(i);
while (i < expr.length() && currentChar != '\'') {
currentChar = expr.charAt(i);
i++;
}
}
// If the character is a supported operator
if (operators[priority].indexOf(currentChar) >= 0) {
// if the character is '%', it's a 3 character operator like
// "%*%" or "%/%", or the mod operator "%%"
if (currentChar == '%') {
int nbChar = 3;
// If it is the mod operator "%%"
if (expr.charAt(i + 1) == '%') {
nbChar = 2;
}
// Find the beginning of the left term
int startingIndex = getPreviousExpressionFirstIndex(expr, i, previousStoppingCharacters);
String prevExp = expr.substring(startingIndex, i);
// If the left expression is not only whitespace (for
// example a = -4 or a = +4)
if (prevExp.trim().length() > 0) {
// Find the end of the right term
int endingIndex = getNextExpressionLastIndex(expr, i + nbChar - 1, nextStoppingCharacters);
String operatorName = operatorsMap.get(expr.substring(i, i + nbChar));
StringBuilder resultExpr = new StringBuilder();
resultExpr.append(operatorName);
resultExpr.append("(");
resultExpr.append(prevExp);
resultExpr.append(",");
resultExpr.append(expr.substring(i + nbChar, endingIndex + 1));
resultExpr.append(")");
// Add a parenthesis if the operator is "%/%" because we
// add floor(..)
if (expr.charAt(i + 1) == '/') {
resultExpr.append(")");
}
expr = expr.substring(0, startingIndex) + resultExpr
+ expr.substring(endingIndex + 1, expr.length());
// Decrement i to be sure to not miss an operator
i = startingIndex - 1;
}
}
// if the next character is not and operator or "=" (not supported yet)
else if (!("+*/=".indexOf(expr.charAt(i + 1)) >= 0)) {
// Find the beginning of the left term
int startingIndex = getPreviousExpressionFirstIndex(expr, i, previousStoppingCharacters);
String prevExp = expr.substring(startingIndex, i);
if(!prevExp.trim().equals("return")) {
// If the left expression is not only whitespace (for
// example a = -4 or a = +4)
if (prevExp.trim().length() > 0) {
// Find the end of the right term
int endingIndex = getNextExpressionLastIndex(expr, i, nextStoppingCharacters);
String operatorName = operatorsMap.get(currentChar + "");
StringBuilder resultExpr = new StringBuilder();
// Add a "+" operator before:
// Example: 1*2 -5*6 will be
// mult(1,2) + mult(-5,6) with a '+' between the two mult operators
resultExpr.append(" + ");
resultExpr.append(operatorName);
resultExpr.append("(");
resultExpr.append(prevExp);
resultExpr.append(",");
resultExpr.append(expr.substring(i + 1, endingIndex + 1));
resultExpr.append(")");
expr = expr.substring(0, startingIndex) + resultExpr
+ expr.substring(endingIndex + 1, expr.length());
//Remove + operator if it is after a "return, if, else, (, [, {,),],},=,+,-
expr = expr.replaceAll("(return|if|else|\\(|\\{|\\|[\\|]|\\}|=|,|<|>) *\\+", "$1");
expr = expr.replaceAll("\\+ +\\+", "+");
expr = expr.replaceAll("\\- +\\+", "-");
expr = expr.replaceAll("\\* +\\+", "*");
expr = expr.replaceAll("\\/ +\\+", "/");
expr = expr.replaceAll("\\: +\\+", ":");
expr = expr.replaceAll("\\^ +\\+", "^");
expr = expr.replaceAll("^ *\\+", "");
// Decrement i to be sure to not miss an operator
i = startingIndex - 1;
}
}
} else {
i = i + 1;
}
}
i = i + 1;
if (i >= expr.length()) {
if (priority < 2) {
priority++;
if(priority == 2)
nextStoppingCharacters+="-";
i = 0;
} else {
continueReplacing = false;
}
}
}
return expr;
}
private void addGlobalVariables(String[] variables) {
if (variablesSet == null) {
variablesSet = new HashSet();
}
for (String variable : variables) {
variablesSet.add(variable);
}
}
/**
* Store global variables in the List variablesList.
*
* @param expr - the expression containing global variables
*/
private void storeGlobalVariables(String expr) {
if (variablesSet == null) {
variablesSet = new HashSet();
}
int equalIndex = getNextExpressionLastIndex(expr, -1, "=") + 1;
int exprLength = expr.length();
while (equalIndex < exprLength) {
if((equalIndex>0 && expr.charAt(equalIndex-1)=='=') || (equalIndex 0 && expr.charAt(startingIndex) == ' ') {
startingIndex--;
}
// Stop at the first stopping character
for (int i = startingIndex; i >= 0; i--) {
char currentChar = expr.charAt(i);
if (currentChar == ')') {
parenthesis++;
} else if (currentChar == '(') {
parenthesis--;
if (parenthesis < 0) {
return i + 1;
}
} else if (currentChar == '}') {
brackets++;
} else if (currentChar == '{') {
brackets--;
if (brackets < 0) {
return i + 1;
}
} else if (currentChar == ']') {
brackets2++;
} else if (currentChar == '[') {
brackets2--;
if (brackets2 < 0) {
return i + 1;
}
} else if (parenthesis == 0 && brackets == 0 && brackets2 == 0
&& stoppingCharacters.indexOf(currentChar) >= 0) {
// If it's a stopping character
return i + 1;
}
}
// If there is no stopping character, the expression start at the
// beginning of the sentence
return firstIndex;
}
/**
* Get the index of the end of an expression The function starts at the
* given startIndex and return the index of the first character founded in
* the stoppingCharacters string. This function ignore characters inside
* brackets or paranthesis
*
* @param expr - the expression to check
* @param startIndex
* - index to start with to search a stopping characters
* @param stoppingCharacters
* - a String containing stopping characters.
* @return the last index of the next expression
*/
private static int getNextExpressionLastIndex(String expr, int startIndex, String stoppingCharacters) {
int lastIndex = expr.length() - 1;
int parenthesis = 0; // '(' and ')'
int brackets = 0; // '{' and '}'
int brackets2 = 0; // '[' and ']'
// Ignore space character at beginning
int startingIndex = startIndex + 1;
while (startingIndex < expr.length() && expr.charAt(startingIndex) == ' ') {
startingIndex++;
}
// Stop at the first stopping character
for (int i = startingIndex; i < expr.length(); i++) {
char currentChar = expr.charAt(i);
if (currentChar == '(') {
parenthesis++;
} else if (currentChar == ')') {
parenthesis--;
if (parenthesis < 0) {
return i - 1;
}
} else if (currentChar == '{') {
brackets++;
} else if (currentChar == '}') {
brackets--;
if (brackets < 0) {
return i - 1;
}
} else if (currentChar == '[') {
brackets2++;
} else if (currentChar == ']') {
brackets2--;
if (brackets2 < 0) {
return i - 1;
}
} else if (parenthesis == 0 && brackets == 0 && brackets2 == 0
&& stoppingCharacters.indexOf(currentChar) >= 0) {
// If it's a stopping character
return i - 1;
}
}
// If there is no stopping character, the expression stop at the
// end of the sentence
return lastIndex;
}
/**
* This function replace default arguments of R function to interpret them
* in javascript
*
* Default argument (in R: "function(arg = defaultValue) {...}") is defined
* after the version ES6 of javascript Java8 uses a previous version of
* javascript so we have to transform this expression to: function(arg) {
* arg = typeof arg !== 'undefined' ? arg : defaultValue; ...}
*
* If an element haven't default value, it will be set to 'null'
* automatically
*
* @param arguments - the R expression with default arguments
* @return the javascript expression with default arguments
*/
private static String replaceFunctionDefaultArguments(String arguments) {
// Linked hash map to keep the same order of arguments
Map parametersAndValuesMap = new LinkedHashMap<>();
// Put in map arguments with there values associated
Matcher matcher = Pattern.compile("([\\w\\-]+) *=* *(([\\w\\-]+))?").matcher(arguments);
while (matcher.find()) {
parametersAndValuesMap.put(matcher.group(1), matcher.group(2));
}
// Result string of arguments (we remove '= value' statement)
StringBuilder resultParameters = new StringBuilder();
// Result string of inside function
StringBuilder resultValues = new StringBuilder();
// True if it is the first element of the map
boolean first = true;
for (Map.Entry entry : parametersAndValuesMap.entrySet()) {
String param = entry.getKey();
String value = entry.getValue();
if (!first) {
resultParameters.append(",");
} else {
first = false;
}
resultParameters.append(param);
resultValues.append(param);
resultValues.append(" = typeof ");
resultValues.append(param);
resultValues.append(" !== 'undefined' ? ");
resultValues.append(param);
resultValues.append(" : ");
resultValues.append(value);
resultValues.append("; ");
}
String result = "(" + resultParameters + ") {" + resultValues;
return result;
}
@Override
public void end() {
js = null;
super.end();
}
@Override
public boolean isAvailable() {
return true;
}
@Override
boolean isWindows() {
return RserveDaemon.isWindows();
}
@Override
boolean isMacOSX() {
return RserveDaemon.isMacOSX();
}
@Override
boolean isLinux() {
return RserveDaemon.isLinux();
}
@Override
protected boolean silentlyVoidEval(String expression, boolean tryEval) {
String jsExpr = convertRtoJs(expression);
try {
this.js.eval(jsExpr);
} catch (ScriptException e) {
String ls = "?";
try {
ls = (this.js.eval("JSON.stringify(" + THIS_ENVIRONMENT + ")")).toString();
} catch (Exception ee) {
ls = ee.getMessage();
}
log("Failed to evaluate code\n```{js}\n"+convertRtoJs(expression)+"\n```\n with variables: "+ls+"\n because: "+e.getMessage(),Level.ERROR);
return false;
}
return true;
}
@Override
protected Object silentlyRawEval(String expression, boolean tryEval) {
Object result = null;
String jsExpr = convertRtoJs(expression);
if (jsExpr != null) {
try {
result = this.js.eval(jsExpr);
} catch (ScriptException e) {
String ls = "?";
try {
ls = (String) this.js.eval("JSON.stringify(" + THIS_ENVIRONMENT + ")").toString();
} catch (Exception ee) {
ls = ee.getMessage();
}
log("Failed to evaluate code\n```{js}\n"+convertRtoJs(expression)+"\n```\n with variables: "+ls+"\n because: "+e.getMessage(),Level.ERROR);
return new RException("Failed to evaluate code\n```{js}\n"+convertRtoJs(expression)+"\n```\n with variables: "+ls+"\n because: "+e.getMessage());
}
}
return result;
}
@Override
public boolean set(String varname, double[][] data, String... names) throws RException {
note_code(varname + " <- " + (data==null?"list()":toRcode(data)));
note_code("names(" + varname + ") <- " + toRcode(names));
note_code(varname + " <- data.frame(" + varname + ")");
// RList list = buildRList(data, names);
// log(HEAD_SET + varname + " <- " + list, Level.INFO);
varname = nameRtoJs(varname);
String allnames = "";
for (int i = 0; i < names.length; i++) {
names[i] = nameRtoJs(names[i]);
allnames = allnames + ",'" + names[i] + "'";
}
allnames = allnames.substring(1);
try {
synchronized (js) {
String dim = "[" + data.length + "," + data[0].length + "]";
String stringMatrix = Arrays.deepToString(data);
js.eval(varname + " = math.reshape(" + stringMatrix + ", " + dim + ")");
js.eval(THIS_ENVIRONMENT + "." + varname + " = " + varname);
js.eval(THIS_ENVIRONMENT + "." + varname + ".names = [" + allnames + "]");
variablesSet.add(varname);
}
} catch (Exception e) {
log(HEAD_ERROR + " " + e.getMessage(), Level.ERROR);
return false;
}
return true;
}
/**
* Set R object in R env.
*
* @param varname
* R object name
* @param var
* R object value
* @return succeeded ?
*/
@Override
public boolean set(String varname, Object var) {
note_code(varname + " <- " + toRcode(var));
varname = nameRtoJs(varname);
try {
synchronized (js) {
// FIXME: find a better solution than this
// For 2d double array, we need to instanciate the matrix with the function "math.reshape"
// I don't know why but the function math.matrix doesn't create the same js object than math.reshape and
// the output ScriptMirrorObject is uncastable in java double[][] array and operations on a math.matrix
// object don't work.
if (var instanceof double[][]) {
double[][] var2DArray = (double[][]) var;
String dim = "[" + var2DArray.length + "," + var2DArray[0].length + "]";
String stringMatrix = Arrays.deepToString(var2DArray);
js.eval(varname + " = math.reshape(" + stringMatrix + ", " + dim + ")");
js.eval(THIS_ENVIRONMENT + "." + varname + " = " + varname);
String allnames = "";
for (int i = 0; i < var2DArray[0].length; i++) {
allnames = allnames + ",'X" + (i + 1) + "'";
}
allnames = allnames.substring(1);
js.eval(THIS_ENVIRONMENT + "." + varname + ".names = [" + allnames + "]");
variablesSet.add(varname);
} else if (var instanceof double[]) {
double[] var1DArray = (double[]) var;
String dim = "[" + var1DArray.length + ",1]";
String stringMatrix = Arrays.toString(var1DArray);
js.eval(varname + " = "+Arrays.toString(var1DArray));
js.eval(THIS_ENVIRONMENT + "." + varname + " = " + varname);
variablesSet.add(varname);
} else {
js.put(varname, var);
js.eval(THIS_ENVIRONMENT + "." + varname + " = " + varname);
variablesSet.add(varname);
}
}
} catch (Exception e) {
log(HEAD_ERROR + " " + e.getMessage(), Level.ERROR);
return false;
}
return true;
}
@Override
public void source(File file) {
if (!file.isFile()) {
throw new IllegalArgumentException("File " + file + " is not reachable.");
}
StringBuilder sb = new StringBuilder();
BufferedReader reader = null;
InputStreamReader isr = null;
FileInputStream fis = null;
String line;
try {
fis = new FileInputStream(file);
isr = new InputStreamReader(fis, "UTF-8");
reader = new BufferedReader(isr);
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
fis.close();
isr.close();
reader.close();
} catch (Exception ee) {
ee.printStackTrace();
}
}
for (String expr : R2jsUtils.parse(sb.toString())) {
silentlyVoidEval(expr, false);
}
}
/**
* delete R variables in R env.
*
* @param vars R objects names
* @return well removed ?
* @throws org.math.R.Rsession.RException Could not do rm
*/
@Override
public boolean rm(String... vars) throws RException {
try {
synchronized (js) {
for(String var : vars) {
js.eval("delete " +THIS_ENVIRONMENT + "." + var+ ";");
variablesSet.remove(var);
js.eval("delete " + var + ";");
}
}
} catch (Exception e) {
log(HEAD_ERROR + " " + e.getMessage(), Level.ERROR);
return false;
}
return true;
}
@Override
public boolean rmAll() {
try {
synchronized (js) {
js.eval("delete " + envName + ";");
variablesSet.clear();
js.eval("var " + envName + " = math.clone({});");
js.eval(THIS_ENVIRONMENT+" = "+envName);
}
} catch (Exception e) {
log(HEAD_ERROR + " " + e.getMessage(), Level.ERROR);
return false;
}
return true;
}
@Override
public String loadPackage(String pack) {
throw new UnsupportedOperationException("Cannot load any package in R2Js. Use 'source' for external static content loading.");
}
@Override
public double asDouble(Object o) throws ClassCastException {
if (o instanceof Double) return (Double)o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (double) ScriptUtils.convert(o, double.class);
}
@Override
public double[] asArray(Object o) throws ClassCastException {
if (o instanceof double[]) return (double[])o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (double[]) ScriptUtils.convert(o, double[].class);
}
@Override
public double[][] asMatrix(Object o) throws ClassCastException {
if (o == null) {
return null;
}
if (o instanceof double[][]) {
return (double[][]) o;
} else if (o instanceof double[]) {
return t(new double[][]{(double[]) o});
} else if (o instanceof Double) {
return new double[][]{{(double) o}};
} else /*if (o instanceof Map)*/ {
double[][] vals = null;
int i = 0;
try {
for (Object k : ((Map) o).keySet()) {
double[] v = null;
if (o instanceof ScriptObjectMirror)
try {
v = (double[]) ((ScriptObjectMirror) o).to(double[].class);
} catch (Exception ex) {
//ex.printStackTrace();
throw new ClassCastException("[asMatrix] Cannot cast ScriptObjectMirror list element to double[] " + ((Map) o).get(k) + " for key " + k + " in " + o);
}
else
try {
v = (double[]) ((Map) o).get(k);
} catch (Exception ex) {
//ex.printStackTrace();
throw new ClassCastException("[asMatrix] Cannot cast list element to double[] " + ((Map) o).get(k) + " for key " + k + " in " + o);
}
if (v == null) {
throw new ClassCastException("[asMatrix] Cannot get list element as double[] " + ((Map) o).get(k) + " for key " + k + " in " + o);
}
if (vals == null) {
vals = new double[v.length][((Map) o).size()];
}
for (int j = 0; j < v.length; j++) {
vals[j][i] = v[j];
}
i++;
}
return vals;
} catch (Exception ex) {
throw new ClassCastException("[asMatrix] Cannot cast Map to matrix: "+ex.getMessage());
}
}
}
@Override
public String asString(Object o) throws ClassCastException {
if (o instanceof String) return (String)o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (String) ScriptUtils.convert(o, String.class);
}
@Override
public String[] asStrings(Object o) throws ClassCastException {
if (o instanceof String[]) return (String[])o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (String[]) ScriptUtils.convert(o, String[].class);
}
@Override
public int asInteger(Object o) throws ClassCastException {
if (o instanceof Integer) return (Integer)o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (int) ScriptUtils.convert(o, int.class);
}
@Override
public int[] asIntegers(Object o) throws ClassCastException {
if (o instanceof int[]) return (int[])o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (int[]) ScriptUtils.convert(o, int[].class);
}
@Override
public boolean asLogical(Object o) throws ClassCastException {
if (o instanceof Boolean) return (Boolean)o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (boolean) ScriptUtils.convert(o, boolean.class);
}
@Override
public boolean[] asLogicals(Object o) throws ClassCastException {
if (o instanceof boolean[]) return (boolean[])o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (boolean[]) ScriptUtils.convert(o, boolean[].class);
}
@Override
public Map asList(Object o) throws ClassCastException {
if (o instanceof Map) return (Map)o; // because already cast in Nashorn/jdk11 (but not in Nashorn/jdk8 !!)
return (Map) ScriptUtils.convert(o, Map.class);
}
@Override
public boolean isNull(Object o) {
if (o==null) return true;
return Arrays.asList(ls()).contains(o.toString());
}
@Override
public String toString(Object o) {
// TODO to test
return o.toString();
}
@Override
public Object cast(Object o) throws ClassCastException {
// If it's a ScriptObjectMirror, it can be an array or a matrix
if (o instanceof Integer) {
return Double.valueOf((int)o);
} else if (o instanceof ScriptObjectMirror) {
try {
// System.err.println("// Casting of the ScriptObjectMirror to a double matrix");
return ((ScriptObjectMirror) o).to(double[][].class);
} catch (Exception e) {//e.printStackTrace();
}
try {
// System.err.println("// Casting of the ScriptObjectMirror to a string array");
String[] stringArray = ((ScriptObjectMirror) o).to(String[].class);
// System.err.println("// Check if the String[] array can be cast to a double[] array");
try {
for (String string : stringArray) {
Double.valueOf(string);
}
} catch (Exception e) {//e.printStackTrace();
// It can't be cast to double[] so we return String[]
return stringArray;
}
// System.err.println("// return double[] array");
return ((ScriptObjectMirror) o).to(double[].class);
} catch (Exception e) {//e.printStackTrace();
}
try {
// System.err.println("// Casting of the ScriptObjectMirror to a double array");
return ((ScriptObjectMirror) o).to(double[].class);
} catch (Exception e) {//e.printStackTrace();
}
try {
// System.err.println(" // Casting of the ScriptObjectMirror to a list/map");
Map m = ((ScriptObjectMirror) o).to(Map.class);
try {
return asMatrix(m);
} catch (ClassCastException c) {
//c.printStackTrace();
return m;
}
} catch (Exception e) {//e.printStackTrace();
}
throw new IllegalArgumentException("Impossible to cast object: ScriptObjectMirror");
} else {
return o;
}
}
Map> envVariables = new HashMap<>();
@Override
public void setGlobalEnv(String envName) {
if (envName == null) {
envName = ENVIRONMENT_DEFAULT;
} else {
envName = "__" + envName + "__";
}
try {
if (asLogical(js.eval("typeof " + envName + " == 'undefined'"))) {// env still not exists
js.eval("var " + envName + " = math.clone({});");
}
js.eval(THIS_ENVIRONMENT+" = "+envName);
} catch (ScriptException ex) {
Log.Err.println(ex.getMessage());
}
String oldEnv = this.envName;
envVariables.put(oldEnv, new TreeSet(variablesSet));
variablesSet.clear();
if (envVariables.containsKey(envName))
variablesSet.addAll(envVariables.get(envName));
this.envName = envName;
}
@Override
public void copyGlobalEnv(String envName) {
if (envName == null) {
envName = ENVIRONMENT_DEFAULT;
} else {
envName = "__" + envName + "__";
}
try {
if (asLogical(js.eval("typeof " + envName + " == 'undefined'"))) // env still not exists
js.eval("var " + envName + " = math.clone({});");
} catch (ScriptException ex) {
Log.Err.println(ex.getMessage());
}
String[] ls = ls(true);
for (String o : ls) {
try {
js.eval(envName + "." + o + " = " + this.envName + "." + o);
} catch (ScriptException ex) {
Log.Err.println(ex.getMessage());
}
}
if (!envVariables.containsKey(envName)) {
envVariables.put(envName, new TreeSet(variablesSet));
} else {
envVariables.get(envName).addAll(new TreeSet(variablesSet));
}
}
private static String html_tmpl
= "\n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " "
+ " \n"
+ " \n"
+ "___R___"
+ "
\n"
+ " \n"
+ "\n"
+ " \n"
+ " \n"
+ "";
private static String input_tmpl = " \n";
private static String submit_tmpl = " \n";
// maybe the worst idea I ever had...
public static String HTMLfun(String Rcode, String fun, String... args) {
R2jsSession R = new R2jsSession(System.out, null);
String html = html_tmpl.replace("___JS___", R.convertRtoJs(Rcode).replace(R.THIS_ENVIRONMENT + ".", ""));
html = html.replace("___R___", Rcode);
html = html.replace("___f___", fun);
String inputs = "";
for (int i = 0; i < args.length; i++) {
inputs = inputs + args[i] + ":" + input_tmpl.replace("___ID___", args[i]) + "
";
args[i] = "document.getElementById('" + args[i] + "').value";
}
html = html.replace("___INPUT___", inputs);
html = html.replace("___SUBMIT___", submit_tmpl.replace("___ONCLICK___", "document.write(" + fun + "(" + cat(",", args) + "))"));
return(html);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy