-
Notifications
You must be signed in to change notification settings - Fork 82
Expand file tree
/
Copy pathTermREPL.java
More file actions
393 lines (344 loc) · 16.5 KB
/
TermREPL.java
File metadata and controls
393 lines (344 loc) · 16.5 KB
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
package org.rascalmpl.library.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.jline.reader.EndOfFileException;
import org.rascalmpl.exceptions.RuntimeExceptionFactory;
import org.rascalmpl.ideservices.IDEServices;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.IEvaluatorContext;
import org.rascalmpl.interpreter.result.AbstractFunction;
import org.rascalmpl.library.lang.json.internal.JsonValueWriter;
import org.rascalmpl.repl.BaseREPL;
import org.rascalmpl.repl.http.REPLContentServer;
import org.rascalmpl.repl.http.REPLContentServerManager;
import org.rascalmpl.repl.output.ICommandOutput;
import org.rascalmpl.repl.output.INotebookOutput;
import org.rascalmpl.repl.output.IOutputPrinter;
import org.rascalmpl.repl.output.ISourceLocationCommandOutput;
import org.rascalmpl.repl.output.IWebContentOutput;
import org.rascalmpl.repl.output.MimeTypes;
import org.rascalmpl.repl.output.impl.AsciiStringOutputPrinter;
import org.rascalmpl.repl.output.impl.PrinterErrorCommandOutput;
import org.rascalmpl.repl.parametric.ILanguageProtocol;
import org.rascalmpl.repl.parametric.ParametricReplService;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.uri.URIUtil;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.ValueFactoryFactory;
import org.rascalmpl.values.functions.IFunction;
import com.google.gson.stream.JsonWriter;
import io.usethesource.vallang.IBool;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IInteger;
import io.usethesource.vallang.IMap;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IString;
import io.usethesource.vallang.ITuple;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;
import io.usethesource.vallang.IWithKeywordParameters;
import io.usethesource.vallang.type.Type;
import io.usethesource.vallang.type.TypeFactory;
public class TermREPL {
private final IRascalValueFactory vf;
private final IDEServices service;
private final PrintWriter err;
private ILanguageProtocol lang;
public TermREPL(IRascalValueFactory vf, IDEServices service, PrintWriter _ignoredOut, PrintWriter err) {
this.vf = vf;
this.service = service;
this.err = err;
}
private Path resolveHistoryFile(ISourceLocation historyFile) {
try {
ISourceLocation result = URIResolverRegistry.getInstance().logicalToPhysical(historyFile);
if (result == null || !result.getScheme().equals("file")) {
err.println("Cannot resolve history file to file on disk");
return null;
}
return Path.of(result.getPath());
}
catch (IOException e) {
return null;
}
}
public ITuple newREPL(IConstructor repl, IString title, IString welcome, IString prompt, IString quit,
ISourceLocation history, IFunction handler, IFunction completor, IFunction stacktrace, IEvaluatorContext eval) {
var term = service.activeTerminal();
if (term == null) {
throw RuntimeExceptionFactory.io("No terminal found in IDE service, we cannot allocate a REPL");
}
lang = new TheREPL(vf, title, welcome, prompt, quit, history, handler, completor, stacktrace);
BaseREPL baseRepl;
try {
baseRepl = new BaseREPL(new ParametricReplService(lang, service, resolveHistoryFile(history)), term);
}
catch (Throwable e) {
throw RuntimeExceptionFactory.io(e.getMessage());
}
TypeFactory tf = TypeFactory.getInstance();
IFunction run = vf.function(tf.functionType(tf.voidType(), tf.tupleEmpty(), tf.tupleEmpty()),
(args, kwargs) -> {
try {
baseRepl.run();
}
catch (IOException e) {
throw RuntimeExceptionFactory.io(e);
}
return vf.tuple();
});
IFunction send = vf.function(tf.functionType(tf.voidType(), tf.tupleType(tf.stringType()), tf.tupleEmpty()),
(args, kwargs) -> {
baseRepl.queueCommand(((IString)args[0]).getValue());
return vf.tuple();
});
return vf.tuple(run, send);
}
public static class TheREPL implements ILanguageProtocol {
private final REPLContentServerManager contentManager = new REPLContentServerManager();
private final TypeFactory tf = TypeFactory.getInstance();
private PrintWriter stdout;
private PrintWriter stderr;
private Reader input;
private final String currentPrompt;
private String quit;
private final AbstractFunction handler;
private final AbstractFunction completor;
private final IValueFactory vf;
@SuppressWarnings("unused")
private final AbstractFunction stacktrace; // TODO: this is a requirement to make proper domain-level stack-traces, but we have to use it still
public TheREPL(IValueFactory vf, IString title, IString welcome, IString prompt, IString quit, ISourceLocation history,
IFunction handler, IFunction completor, IValue stacktrace) {
this.vf = vf;
// TODO: these casts mean that TheRepl only works with functions produced by the
// interpreter for now. The reason is that the REPL needs access to environment configuration
// parameters of these functions such as stdout, stdin, etc.
// TODO: rethink the term repl in the compiled context, based on the compiled REPL for Rascal
// which does not exist yet.
this.handler = (AbstractFunction) handler;
this.completor = (AbstractFunction) completor;
this.stacktrace = (AbstractFunction) stacktrace;
this.currentPrompt = prompt.getValue();
this.quit = quit.getValue();
}
@Override
public void initialize(Reader input, PrintWriter stdout, PrintWriter stderr, IDEServices services) {
this.input = input;
this.stdout = stdout;
this.stderr = stdout;
}
@Override
public void cancelRunningCommandRequested() {
handler.getEval().interrupt();
}
@Override
public String getPrompt() {
return currentPrompt;
}
@Override
public ICommandOutput handleInput(String line) throws InterruptedException {
if (line.trim().equals(quit)) {
throw new EndOfFileException();
}
try {
handler.getEval().__setInterrupt(false);
IConstructor content = (IConstructor) call(handler, new Type[] { tf.stringType() }, new IValue[] { vf.string(line) });
if (content.has("id")) {
return handleInteractiveContent(content);
}
else {
IConstructor response = (IConstructor) content.get("response");
switch (response.getName()) {
case "response":
return handlePlainTextResponse(response);
case "fileResponse":
return handleFileResponse(response);
case "jsonResponse":
return handleJSONResponse(response);
default:
return new PrinterErrorCommandOutput("Unexpected constructor: " + response.getName());
}
}
}
catch (IOException e) {
return new PrinterErrorCommandOutput(e.getMessage());
}
catch (Throwable e) {
return new PrinterErrorCommandOutput(e.getMessage());
}
}
private ICommandOutput handleInteractiveContent(IConstructor content) throws IOException, UnsupportedEncodingException {
String id = ((IString) content.get("id")).getValue();
Function<IValue, IValue> callback = liftProviderFunction(content.get("callback"));
REPLContentServer server = contentManager.addServer(id, callback);
return produceHTMLResponse(id, URIUtil.assumeCorrect("http", "localhost:" + server.getListeningPort(), ""));
}
abstract class NotebookWebContentOutput implements INotebookOutput, IWebContentOutput {}
private ICommandOutput produceHTMLResponse(String id, URI URL) throws UnsupportedEncodingException{
return new NotebookWebContentOutput() {
@Override
public IOutputPrinter asNotebook() {
return new IOutputPrinter() {
@Override
public void write(PrintWriter target, boolean unicodeSupported) {
target.println("<script>");
target.println(" var "+ id +" = new Salix('"+ id + "', '" + URL + "');");
target.println(" google.charts.load('current', {'packages':['corechart']});");
target.println(" google.charts.setOnLoadCallback(function () { ");
target.println(" registerCharts("+ id +");");
target.println(" registerDagre(" + id + ");");
target.println(" registerTreeView("+ id +");");
target.println(" " + id + ".start();");
target.println(" });");
target.println("<script>");
target.println("<div id = \"" + id + "\"> </div>");
}
@Override
public String mimeType() {
return MimeTypes.HTML;
}
};
}
@Override
public IOutputPrinter asHtml() {
return new AsciiStringOutputPrinter( "<iframe class=\"rascal-content-frame\" src=\""+ URL +"\"></iframe>", MimeTypes.HTML);
}
@Override
public IOutputPrinter asPlain() {
return new AsciiStringOutputPrinter("Serving visual content at |" + URL + "|", MimeTypes.PLAIN_TEXT);
}
@Override
public URI webUri() {
return URL;
}
@Override
public IString webTitle() {
// TODO: extract from ADT
return ValueFactoryFactory.getValueFactory().string(id);
}
@Override
public IInteger webviewColumn() {
// TODO: extract from ADT
return ValueFactoryFactory.getValueFactory().integer(1);
}
};
}
private Function<IValue, IValue> liftProviderFunction(IValue callback) {
IFunction func = (IFunction) callback;
return (t) -> {
// This function will be called from another thread (the webserver)
// That is problematic if the current repl is doing something else at that time.
// The evaluator is already locked by the outer Rascal REPL (if this REPL was started from `startREPL`).
// synchronized(eval) {
return func.call(t);
};
}
private ICommandOutput handleJSONResponse(IConstructor response) {
IValue data = response.get("val");
IWithKeywordParameters<? extends IConstructor> kws = response.asWithKeywordParameters();
IValue dtf = kws.getParameter("dateTimeFormat");
IValue dai = kws.getParameter("dateTimeAsInt");
IValue ras = kws.getParameter("rationalsAsString");
IValue formatters = kws.getParameter("formatter");
IValue ecn = kws.getParameter("explicitConstructorNames");
IValue edt = kws.getParameter("explicitDataTypes");
IValue fpo = kws.getParameter("fileLocationsAsPathOnly");
JsonValueWriter writer = new JsonValueWriter()
.setCalendarFormat(dtf != null ? ((IString) dtf).getValue() : "yyyy-MM-dd\'T\'HH:mm:ss\'Z\'")
.setFormatters((IFunction) formatters)
.setDatesAsInt(dai != null ? ((IBool) dai).getValue() : true)
.setRationalsAsString(ras != null ? ((IBool) ras).getValue() : false)
.setExplicitConstructorNames(ecn != null ? ((IBool) ecn).getValue() : false)
.setExplicitDataTypes(edt != null ? ((IBool) edt).getValue() : false)
.setFileLocationsAsPathOnly(edt != null ? ((IBool) fpo).getValue() : true)
;
return new ICommandOutput() {
@Override
public IOutputPrinter asPlain() {
return new IOutputPrinter() {
@Override
public void write(PrintWriter target, boolean unicodeSupported) {
try (var json = new JsonWriter(target)) {
writer.write(json, data);
}
catch (IOException ex) {
target.println("Unexpected IO exception: " + ex);
}
}
@Override
public String mimeType() {
return "application/json";
}
};
}
};
}
private ICommandOutput handleFileResponse(IConstructor response)
throws UnsupportedEncodingException {
IString fileMimetype = (IString) response.get("mimeType");
ISourceLocation file = (ISourceLocation) response.get("file");
return new ISourceLocationCommandOutput() {
@Override
public ISourceLocation asLocation() {
return file;
}
@Override
public String locationMimeType() {
return fileMimetype.getValue();
}
@Override
public IOutputPrinter asPlain() {
return new AsciiStringOutputPrinter("Direct file returned, REPL doesn't support file results", MimeTypes.PLAIN_TEXT);
}
};
}
private ICommandOutput handlePlainTextResponse(IConstructor response)
throws UnsupportedEncodingException {
String content = ((IString) response.get("content")).getValue();
String contentMimetype = ((IString) response.get("mimeType")).getValue();
return () -> new AsciiStringOutputPrinter(content, contentMimetype);
}
@Override
public boolean supportsCompletion() {
return true;
}
private IValue call(IFunction f, Type[] types, IValue[] args) {
if (f instanceof AbstractFunction) {
Evaluator eval = (Evaluator) ((AbstractFunction) f).getEval();
synchronized (eval) {
try {
eval.overrideDefaultWriters(input, stdout, stderr);
return f.call(args);
}
finally {
stdout.flush();
stderr.flush();
eval.revertToDefaultWriters();
}
}
}
else {
throw RuntimeExceptionFactory.illegalArgument(f, "term repl only works with interpreter for now");
}
}
@Override
public Map<String, String> completeFragment(String line, String word) {
IMap result = (IMap)call(completor, new Type[] { tf.stringType(), tf.stringType() },
new IValue[] { vf.string(line), vf.string(word) });
var resultMap = new HashMap<String, String>();
var it = result.entryIterator();
while (it.hasNext()) {
var c = it.next();
resultMap.put(((IString)c.getKey()).getValue(), ((IString)c.getValue()).getValue());
}
return resultMap;
}
}
}