-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathRLangPApplet.java
233 lines (210 loc) · 7.53 KB
/
RLangPApplet.java
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
package rprocessing;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.renjin.parser.RParser;
import org.renjin.script.RenjinScriptEngine;
import org.renjin.sexp.Closure;
import org.renjin.sexp.ExpressionVector;
import org.renjin.sexp.FunctionCall;
import org.renjin.sexp.SEXP;
import org.renjin.sexp.Symbol;
import rprocessing.applet.BuiltinApplet;
import rprocessing.exception.NotFoundException;
import rprocessing.util.Constant;
import rprocessing.util.Printer;
/**
* RlangPApplet
* PApplet for R language, powered by Renjin.
*
* @author github.com/gaocegege
*/
public class RLangPApplet extends BuiltinApplet {
private static final boolean VERBOSE = Boolean
.parseBoolean(System.getenv("VERBOSE_RLANG_MODE"));
// A static-mode sketch must be interpreted from within the setup() method.
// All others are interpreted during construction in order to harvest method
// definitions, which we then invoke during the run loop.
private final Mode mode;
/** Program code */
private final String programText;
/** Engine to interpret R code */
private final RenjinScriptEngine renjinEngine;
private final Printer stdout;
/**
* Mode for Processing.
*
* @author github.com/gaocegege
*/
private enum Mode {
STATIC, ACTIVE, MIXED
}
private static void log(String msg) {
if (!VERBOSE) {
return;
}
System.err.println(RLangPApplet.class.getSimpleName() + ": " + msg);
}
public RLangPApplet(final String programText, final Printer stdout) throws NotFoundException {
// Create a script engine manager.
ScriptEngineManager manager = new ScriptEngineManager();
// Create a Renjin engine.
ScriptEngine engine = manager.getEngineByName("Renjin");
// Check if the engine has loaded correctly.
if (engine == null) {
throw new NotFoundException("Renjin Script Engine not found on the classpath.");
}
this.renjinEngine = (RenjinScriptEngine) engine;
this.programText = programText;
this.stdout = stdout;
this.prePassCode();
// Detect the mode after pre-pass program code.
this.mode = this.detectMode();
}
/**
* Evaluate all the function calls.
*/
public void prePassCode() {
SEXP source = RParser.parseSource(this.programText + "\n", "inline-string");
if (isSameClass(source, ExpressionVector.class)) {
ExpressionVector ev = (ExpressionVector) source;
for (int i = 0; i < ev.length(); ++i) {
/**
* There is a bug, see https://github.com/gaocegege/Processing.R/issues/7
* For example, processing$line() is also a function call in renjin engine.
* To solve this problem, add a hack to make sure the function is "<-".
*/
if (isSameClass(ev.get(i), FunctionCall.class)
&& isSameClass(((FunctionCall) ev.get(i)).getFunction(), Symbol.class)
&& ((Symbol) ((FunctionCall) ev.get(i)).getFunction()).getPrintName()
.equals("<-")) {
this.renjinEngine.getTopLevelContext().evaluate(ev.get(i),
this.renjinEngine.getTopLevelContext().getEnvironment());
}
}
}
}
/**
* Detect the mode.
* After: prePassCode()
*/
private Mode detectMode() {
if (isActiveMode()) {
if (isMixMode()) {
return Mode.MIXED;
}
return Mode.ACTIVE;
}
return Mode.STATIC;
}
/**
* Add PApplet instance to R top context
* Notice: DO NOT do it in constructor.
*/
public void addPAppletToRContext() {
this.renjinEngine.put(Constant.PROCESSING_VAR_NAME, this);
// This is a trick to be deprecated. It is used to print
// messages in Processing app console by stdout$print(msg).
this.renjinEngine.put("stdout", stdout);
}
/**
* @see processing.core.PApplet#settings()
*/
@Override
public void settings() {
Object obj = this.renjinEngine.get(Constant.SETTINGS_NAME);
if (obj.getClass().equals(Closure.class)) {
((Closure) obj).doApply(this.renjinEngine.getTopLevelContext());
} else if (mode == Mode.STATIC) {
// TODO: Implement Static Mode.
// Set size and something else.
}
}
/**
* Evaluate the program code.
* @see processing.core.PApplet#setup()
*/
@Override
public void setup() {
// I don't know why I put it there. Now I think it should be in constructor.
// But I ...
wrapProcessingVariables();
if (this.mode == Mode.STATIC) {
try {
this.renjinEngine.eval(this.programText);
} catch (ScriptException e) {
log(e.toString());
}
} else if (this.mode == Mode.ACTIVE) {
Object obj = this.renjinEngine.get(Constant.SETUP_NAME);
if (obj.getClass().equals(Closure.class)) {
((Closure) obj).doApply(this.renjinEngine.getTopLevelContext());
}
} else {
System.out.println("The program is in mix mode now.");
}
}
/**
* Call the draw function in R script.
* @see processing.core.PApplet#draw()
*/
@Override
public void draw() {
Object obj = this.renjinEngine.get(Constant.DRAW_NAME);
if (obj.getClass().equals(Closure.class)) {
((Closure) obj).doApply(this.renjinEngine.getTopLevelContext());
}
}
/*
* Helper functions
*/
/**
* Detect whether the program is in active mode.
*
* @return
*/
@SuppressWarnings("rawtypes")
private boolean isActiveMode() {
Class closureClass = Closure.class;
return isSameClass(this.renjinEngine.get(Constant.SETTINGS_NAME), closureClass)
|| isSameClass(this.renjinEngine.get(Constant.SETUP_NAME), closureClass)
|| isSameClass(this.renjinEngine.get(Constant.DRAW_NAME), closureClass);
}
/**
* Detect whether the program is in mix mode.
* After: isActiveMode()
*
* @return
*/
@SuppressWarnings("rawtypes")
private boolean isMixMode() {
Class closureClass = Closure.class;
return isSameClass(this.renjinEngine.get(Constant.SIZE_NAME), closureClass);
}
/**
* Set Environment variables in R top context.
*/
protected void wrapProcessingVariables() {
log("Wrap Processing built-in variables into R top context.");
this.renjinEngine.put("width", width);
this.renjinEngine.put("height", height);
this.renjinEngine.put("displayWidth", displayWidth);
this.renjinEngine.put("displayHeight", displayHeight);
this.renjinEngine.put("focused", focused);
this.renjinEngine.put("keyPressed", keyPressed);
this.renjinEngine.put("frameCount", (double) frameCount);
this.renjinEngine.put("frameRate", frameRate);
// TODO: Find some ways to push constants into R.
}
/**
* Return whether the object has same class with clazz.
*
* @param obj
* @param clazz
* @return
*/
@SuppressWarnings("rawtypes")
private static boolean isSameClass(Object obj, Class clazz) {
return obj.getClass().equals(clazz);
}
}