This repository has been archived by the owner on Feb 22, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathR.mjs
397 lines (393 loc) · 12.2 KB
/
R.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
"use strict";
/**
* @file libr-bridge: Bridging module between JavaScript and R
* @author TAKAHASHI, Kyohei <[email protected]>
* @version XXXX
*/
import ref from "ref";
import ffi from "ffi";
import createLibR, {ParseStatus} from "./libR";
import SEXPWrap from "./SEXPWrap";
import debug_ from "debug";
const debug = debug_("libr-bridge:class R");
let R_isInitialized = false;
let R_GlobalEnv = undefined;
let func_cached = {};
let libR = undefined;
/**
* Class for accessing R via libR.
*/
export default class R{
/**
* Constructor function for class R
*/
constructor(){
if(!R.isInitialized()){
debug("Initializing R...");
const argv = ["REmbeddedOnBridge", "--vanilla", "--gui=none", "--silent"];
libR = createLibR();
if(libR === void 0){
debug("Failed to initialize");
throw new "R initialization failed.";
}
libR.Rf_initEmbeddedR(argv.length, argv);
libR.R_setStartTime();
/* Initialize values */
R_GlobalEnv = new SEXPWrap(libR.R_GlobalEnv.deref()) ; //new SEXPWrap(libR.R_sysframe(0, ref.NULL));
// R_GlobalEnv.preserve();
R.R_NilValue = libR.Rf_GetRowNames(R.GlobalEnv); // passing non vector returns Nil
if(!(new SEXPWrap(R.R_NilValue)).isNull()){
throw new Error("Can not acquire NilValue");
}
R.R_UnboundValue = libR.Rf_findVar(libR.Rf_install("__non_existing_value_kcrt__"), R.GlobalEnv);
R.R_NamesSymbol = libR.Rf_install("names");
R.R_NaInt = this.eval("as.integer(NA)"); // usually INT_MIN (-2147483648)
//R.R_NaString = new SEXPWrap(libR.STRING_ELT(this.eval_raw("as.character(NA)").sexp, 0));
R.R_NaString = new SEXPWrap(libR.R_NaString.deref());
R.R_BlankString = new SEXPWrap(libR.R_BlankString.deref());
R_isInitialized = true;
}
this.__initializeRfunc();
}
/**
* Load some R functions.
* Please do not call manually.
* @private
*/
__initializeRfunc(){
let funclist = ["print", "require", "mean", "cor", "var", "sd", "sqrt", "IQR", "min", "max",
"range", "fisher.test", "t.test", "wilcox.test", "prop.test", "var.test", "p.adjust",
"sin", "cos", "tan", "sum", "c",
"is.na", "is.nan", "write.csv", "data.frame"];
funclist.map((e) => {this[e] = this.func(e);});
}
/**
* Check whether R class is globally initialized or not.
* @return {boolean} Returns true if R is already loaded.
*/
static isInitialized(){
return R_isInitialized;
}
/**
* Acquire global environment in R.
* @return {boolean} SEXP of global environment.
*/
static get GlobalEnv(){
return R_GlobalEnv.sexp;
}
/**
* Initialized libR object for accessing R.
* @return {Object} libR
*/
static get libR() {
return libR;
}
/**
* Acquire bridging function to access R function.
* Functions receive JavaScript value, and returns JavaScript compatible objects.
* @param {string} name name of R function
* @return {function} Bridging function
* @example
* const sum = R.func("sum")
* console.log(sum([1, 2, 3])) // prints 6
*/
func(name){
return this.__RFuncBridge.bind(this, this.__func_sexp(name));
}
/**
* Acquire bridging function to access R function.
* This function doesn't convert to/from SEXP.
* Receives SEXPWrap, and returns SEXPWrap. Please use carefully.
* @param {string} name name of R function
* @return {function} Bridging function
* @see {@link R#func}
*/
func_raw(name){
return this.__RFuncBridge_raw.bind(this, this.__func_sexp(name));
}
/**
* Find functions in R environment.
* Please do not call this function manually.
* @private
* @param {string} name name of function
* @return {SEXPWrap} SEXPWrap object of R function
*/
__func_sexp(name){
if(!(name in func_cached)){
const func_sexp = libR.Rf_findFun(libR.Rf_install(name), R.GlobalEnv);
func_cached[name] = new SEXPWrap(func_sexp);
func_cached[name].preserve(); // Unfortunately, we have no destructor in JavaScript....
}
return func_cached[name];
}
/**
* Bridging function for R function.
* This bridging function doesn't handle SEXP.
* Please do not call this function manually.
* @private
* @param {function} _func SEXPWrap object of R function
* @return {SEXPWrap} SEXPWrap object of returned value
*/
__RFuncBridge_raw(_func){
// Function name with "raw" receives SEXP, and returns SEXP
let lang;
R.range(0, arguments.length).reverse().map((i) => {
if(lang === void 0){
lang = new SEXPWrap(libR.Rf_lcons(arguments[i].sexp, R.R_NilValue));
}else{
lang.protect();
lang = new SEXPWrap(libR.Rf_lcons(arguments[i].sexp, lang.sexp));
lang.unprotect(1); // this frees old lang
}
if(arguments[i].argtag !== void 0){
libR.SET_TAG(lang.sexp, libR.Rf_install(arguments[i].argtag));
}
});
lang.protect(); // protect the most recent one.
try{
const ret = this.__eval_langsxp(lang.sexp);
lang.unprotect();
return ret;
}catch(e){
lang.protect();
throw e;
}
}
/**
* Bridging function for R function.
* Please do not call this function manually.
* @private
* @param {function} func SEXPWrap object of R function
* @return JavaScript compatible returned value
*/
__RFuncBridge(func){
// This receives normal Javascript value, and returns Javascript value
const argumentsArr = Array.apply(null, arguments).slice(1);
let argumentsSEXPWrap = new Array();
argumentsArr.map((e) => {
if(e.constructor.name == "Object"){
// add all items
for(let key of Object.keys(e)){
const sw = new SEXPWrap(e[key]);
sw.protect();
sw.argtag = key;
argumentsSEXPWrap.push(sw);
}
}else{
// simply add
const sw = new SEXPWrap(e);
sw.protect();
argumentsSEXPWrap.push(sw);
}
});
try {
let ret_sexp = this.__RFuncBridge_raw(func, ...argumentsSEXPWrap);
ret_sexp.protect();
let ret = ret_sexp.getValue();
ret_sexp.unprotect();
SEXPWrap.unprotect(argumentsSEXPWrap.length);
return ret;
}catch(e){
SEXPWrap.unprotect(argumentsSEXPWrap.length);
throw e;
}
}
/**
* Execute R code.
* @param {string} code R code
* @param {boolean} silent Suppress error message if true.
* @throws {Error} When execution fails.
* @return {SEXPWrap} SEXPWrap object of returned value. Returns undefined on error.
* @see {@link eval}, R_ParseEvalString
*/
eval_raw(code, silent=false){
const s = new SEXPWrap(code);
s.protect();
var status = ref.alloc(ref.types.int);
const ps = new SEXPWrap(libR.R_ParseVector(s.sexp, -1, status, R.R_NilValue));
ps.protect();
if(ref.deref(status) != ParseStatus.PARSE_OK ||
!(ps.isExpression()) ||
ps.length() != 1){
ps.unprotect(2);
debug(`Parse error.\n-----\n${code}\n-----`);
throw new Error("Parse error of R code");
}else{
try {
const ret = this.__eval_langsxp(libR.VECTOR_ELT(ps.sexp, 0), silent);
ps.unprotect(2);
return ret;
}catch(e){
const errmsg = libR.R_curErrorBuf();
debug(`Execution error in eval_raw.\n----\n${code}\n\nReason: ${errmsg}----`);
ps.unprotect(2);
throw e;
}
}
}
/**
* Execute R code with LANGSXP
* @private
*/
__eval_langsxp(langsxp, silent=false){
var errorOccurred = ref.alloc(ref.types.int, 0);
const f = silent ? libR.R_tryEvalSilent : libR.R_tryEval;
const retval = new SEXPWrap(f(langsxp, R.GlobalEnv, errorOccurred));
if(ref.deref(errorOccurred)){
const errmsg = libR.R_curErrorBuf();
throw new Error(`Execution error: ${errmsg}`);
}
return retval;
}
/**
* Execute R code without error handling. App crashes when execution/parse failure.
* Please use this function with care.
* @param {string} code R code
* @return {SEXPWrap} SEXPWrap object of returned value. Returns undefined on error.
* @see {@link eval_raw}
*/
eval_direct(code){
return new SEXPWrap(libR.R_ParseEvalString(code, R.GlobalEnv));
}
/**
* Execute R code.
* @param {string} code R code
* @param {boolean} silent Suppress error message if true.
* @throws {Error} When execution fails.
* @return JavaScript compatible object of returned value.
* @example
* let value = R.eval("sum(c(1, 2, 3))") // value will be 6
*/
eval(code, silent=false){
const ret = this.eval_raw(code, silent);
ret.protect();
const retval = ret.getValue();
ret.unprotect();
return retval;
}
/**
* Execute R code with R try. This is more safe than {@link R#eval}.
* @param {string} code R code
* @param {boolean} silent Suppress error message if true.
* @return Returned value. Returns undefined on error.
*/
evalWithTry(code, silent=false){
const f = silent ? "TRUE" : "FALSE";
return this.eval(`try({${code}}, silent=${f})`);
}
/**
* Acquire value of R variable
* @param {string} varname Name of variable
* @return Value in the R variable.
*/
getVar(varname){
let varsexp = new SEXPWrap(libR.Rf_findVar(libR.Rf_install(varname), R.GlobalEnv));
if(varsexp.sexp.address() == R.R_UnboundValue.address()){ return undefined; }
varsexp.protect();
let value = varsexp.getValue();
varsexp.unprotect();
return value;
}
/**
* Acquire names attribute of R variable
* @param {string} varname Name of variable
* @return {string} Associated name attribute for the specified R variable. If no name, undefined will be returned.
*/
getVarNames(varname){
let varsexp = new SEXPWrap(libR.Rf_findVar(libR.Rf_install(varname), R.GlobalEnv));
return varsexp.names;
}
/**
* Set value to R variable
* @param {string} varname Name of variable
* @param {object} value Value you want to set to variable.
*/
setVar(varname, value){
let value_sexp = new SEXPWrap(value);
value_sexp.protect();
libR.Rf_setVar(libR.Rf_install(varname), value_sexp.sexp, R.GlobalEnv);
value_sexp.unprotect();
}
/**
* Set names attribute to R variable
* @param {string} varname Name of variable
* @param {object} value Value you want to set to names attributes
*/
setVarNames(varname, value){
let varsexp = new SEXPWrap(libR.Rf_findVar(libR.Rf_install(varname), R.GlobalEnv));
varsexp.names = value;
}
/**
* Use your own console input/output instead of R's default one.
* @param {function} onMessage Function on showing message
*/
overrideShowMessage(onMessage){
const ShowMessage = ffi.Callback("void", [ref.types.CString], (msg) => onMessage(msg) );
ref.writePointer(libR.ptr_R_ShowMessage, 0, ShowMessage);
}
/**
* Use your own console input/output instead of R's default one.
* @param {function} onReadConsole Function on console read
*/
overrideReadConsole(onReadConsole){
const ReadConsole = ffi.Callback("int", [ref.types.CString, ref.refType(ref.types.char), "int", "int"],
(prompt, buf, len, _addtohistory) => {
debug("Read console start: " + prompt);
const ret = onReadConsole(prompt) + "\n";
const rebuf = ref.reinterpret(buf, len, 0);
if(ret.length + 1 > len){
/* too large! */
debug("Too long input for ReadConsole");
ref.writeCString(rebuf, 0, "ERROR");
}else{
debug("Writedown to buffer.");
ref.writeCString(rebuf, 0, ret);
}
debug("Read console fin");
return 1;
});
ref.writePointer(libR.ptr_R_ReadConsole, 0, ReadConsole);
}
/**
* Use your own console input/output instead of R's default one.
* @param {function} onWriteConsole Function on console write
*/
overrideWriteConsole(onWriteConsole){
const WriteConsole = ffi.Callback("void", [ref.types.CString, "int"], (output, _len) => onWriteConsole(output) );
const WriteConsoleEx = ffi.Callback(
"void", [ref.types.CString, "int", "int"],
(output, len, otype) => onWriteConsole(output, otype)
);
ref.writePointer(libR.ptr_R_WriteConsole, 0, WriteConsole);
ref.writePointer(libR.ptr_R_WriteConsoleEx, 0, WriteConsoleEx);
}
/**
* Set callback on R's computation.
* @param {function} onBusy Function called on busy/job finish
*/
overrideBusy(onBusy){
const Busy = ffi.Callback("void", ["int"], (which) => onBusy(which));
ref.writePointer(libR.ptr_R_Busy, 0, Busy);
}
/**
* Finish using R.
*/
release(){
libR.Rf_endEmbeddedR(0);
}
/**
* Python like range function.
* Be careful, this is not R ':' operator
* range(0, 3) == [0, 1, 2], which is not eq. to 0:3
* @param {integer} a from
* @param {integer} b to (this value won't be included)
* @return {Array} value in a <= x < b. range(0, 3) == [0, 1, 2]
*/
static range(a, b){
let len = (b - a);
return [...Array(len)].map((e, i) => i + a);
}
}
/*
* vim: filetype=javascript
*/