Skip to content

Commit 39a03ee

Browse files
committed
Rewrote repl integration for the tutor
1 parent c9f6ac0 commit 39a03ee

5 files changed

Lines changed: 114 additions & 142 deletions

File tree

src/org/rascalmpl/repl/rascal/RascalInterpreterREPL.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,11 @@ public void cancelRunningCommandRequested() {
180180
eval.endAllJobs();
181181
}
182182

183+
public void cleanEnvironment() {
184+
Objects.requireNonNull(eval, "Not initialized yet");
185+
eval.getCurrentModuleEnvironment().reset();
186+
}
187+
183188
@Override
184189
public ICommandOutput stackTraceRequested() {
185190
Objects.requireNonNull(eval, "Not initialized yet");
Lines changed: 100 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,63 @@
11
package org.rascalmpl.tutor.lang.rascal.tutor.repl;
22

3-
import java.io.BufferedInputStream;
4-
import java.io.ByteArrayInputStream;
53
import java.io.ByteArrayOutputStream;
64
import java.io.File;
75
import java.io.IOException;
86
import java.io.InputStream;
9-
import java.io.InputStreamReader;
107
import java.io.OutputStream;
11-
import java.io.UnsupportedEncodingException;
8+
import java.io.PrintWriter;
9+
import java.io.Reader;
10+
import java.io.StringWriter;
1211
import java.lang.reflect.InvocationTargetException;
1312
import java.net.URISyntaxException;
1413
import java.nio.charset.StandardCharsets;
1514
import java.nio.file.Paths;
16-
import java.util.Arrays;
1715
import java.util.Base64;
1816
import java.util.HashMap;
1917
import java.util.Map;
18+
19+
import org.jline.terminal.Terminal;
20+
import org.jline.terminal.TerminalBuilder;
21+
import org.rascalmpl.debug.IRascalMonitor;
2022
import org.rascalmpl.ideservices.IDEServices;
2123
import org.rascalmpl.interpreter.Evaluator;
22-
import org.rascalmpl.interpreter.env.GlobalEnvironment;
23-
import org.rascalmpl.interpreter.env.ModuleEnvironment;
24-
import org.rascalmpl.interpreter.load.StandardLibraryContributor;
2524
import org.rascalmpl.interpreter.utils.RascalManifest;
26-
import org.rascalmpl.library.Prelude;
25+
import org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages;
2726
import org.rascalmpl.library.util.PathConfig;
28-
import org.rascalmpl.repl.RascalInterpreterREPL;
27+
import org.rascalmpl.parser.gtd.exception.ParseError;
28+
import org.rascalmpl.repl.StopREPLException;
29+
import org.rascalmpl.repl.output.IBinaryOutputPrinter;
30+
import org.rascalmpl.repl.output.IErrorCommandOutput;
31+
import org.rascalmpl.repl.output.IImageCommandOutput;
32+
import org.rascalmpl.repl.output.IWebContentOutput;
33+
import org.rascalmpl.repl.rascal.RascalInterpreterREPL;
2934
import org.rascalmpl.shell.ShellEvaluatorFactory;
3035
import org.rascalmpl.uri.URIResolverRegistry;
3136
import org.rascalmpl.uri.URIUtil;
3237
import org.rascalmpl.uri.classloaders.SourceLocationClassLoader;
3338
import org.rascalmpl.uri.project.ProjectURIResolver;
3439
import org.rascalmpl.uri.project.TargetURIResolver;
35-
import org.rascalmpl.values.ValueFactoryFactory;
3640

3741
import io.usethesource.vallang.IList;
3842
import io.usethesource.vallang.ISourceLocation;
3943
import io.usethesource.vallang.IValue;
40-
import io.usethesource.vallang.IValueFactory;
44+
import io.usethesource.vallang.io.StandardTextWriter;
4145

4246
public class TutorCommandExecutor {
43-
private final RascalInterpreterREPL repl;
44-
private final ByteArrayOutputStream shellStandardOutput;
45-
private final ByteArrayOutputStream shellErrorOutput;
47+
private final RascalInterpreterREPL interpreter;
48+
private final StringWriter outWriter = new StringWriter();
49+
private final PrintWriter outPrinter = new PrintWriter(outWriter);
50+
private final StringWriter errWriter = new StringWriter();
51+
private final PrintWriter errPrinter = new PrintWriter(errWriter, true);
4652
private final ITutorScreenshotFeature screenshot;
4753

4854
public TutorCommandExecutor(PathConfig pcfg) throws IOException, URISyntaxException{
49-
shellStandardOutput = new ByteArrayOutputStream();
50-
shellErrorOutput = new ByteArrayOutputStream();
51-
ByteArrayInputStream shellInputNotUsed = new ByteArrayInputStream("***this inputstream should not be used***".getBytes());
52-
repl = new RascalInterpreterREPL(false, false, null) {
55+
interpreter = new RascalInterpreterREPL() {
5356
@Override
54-
protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services) {
55-
GlobalEnvironment heap = new GlobalEnvironment();
56-
ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap));
57-
IValueFactory vf = ValueFactoryFactory.getValueFactory();
58-
Evaluator eval = new Evaluator(vf, input, stderr, stdout, root, heap, services);
59-
60-
eval.addRascalSearchPathContributor(StandardLibraryContributor.getInstance());
61-
eval.setMonitor(services);
57+
protected Evaluator buildEvaluator(Reader input, PrintWriter stdout, PrintWriter stderr,
58+
IDEServices services) {
59+
var eval = super.buildEvaluator(input, stdout, stderr, services);
6260
eval.getConfiguration().setRascalJavaClassPathProperty(javaCompilerPathAsString(pcfg.getJavaCompilerPath()));
63-
eval.setMonitor(services);
6461

6562
ISourceLocation projectRoot = inferProjectRoot((ISourceLocation) pcfg.getSrcs().get(0));
6663
String projectName = new RascalManifest().getProjectName(projectRoot);
@@ -81,13 +78,26 @@ protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, O
8178

8279
return eval;
8380
}
81+
82+
@Override
83+
protected IDEServices buildIDEService(PrintWriter err, IRascalMonitor monitor, Terminal term) {
84+
return (monitor instanceof IDEServices) ? (IDEServices)monitor : new TutorIDEServices(err);
85+
}
86+
8487
};
8588

86-
TutorIDEServices services = new TutorIDEServices();
87-
repl.initialize(shellInputNotUsed, shellStandardOutput, shellErrorOutput, services);
88-
repl.setMeasureCommandTime(false);
89-
90-
this.screenshot = loadScreenShotter();
89+
var terminal = TerminalBuilder.builder()
90+
.system(false)
91+
.streams(InputStream.nullInputStream(), OutputStream.nullOutputStream())
92+
.dumb(true)
93+
.color(false)
94+
.encoding(StandardCharsets.UTF_8)
95+
.build();
96+
97+
98+
99+
interpreter.initialize(Reader.nullReader(), outPrinter, errPrinter, new TutorIDEServices(errPrinter), terminal);
100+
screenshot = loadScreenShotter();
91101
}
92102

93103
private ITutorScreenshotFeature loadScreenShotter() {
@@ -97,14 +107,14 @@ private ITutorScreenshotFeature loadScreenShotter() {
97107
.loadClass("org.rascalmpl.tutor.Screenshotter")
98108
.getDeclaredConstructor()
99109
.newInstance();
100-
}
110+
}
101111
catch (ClassNotFoundException e) {
102112
// that is normal; we just don't have the feature available.
103113
return null;
104114
}
105-
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
106-
throw new Error("WARNING: Could not load screenshot feature from org.rascalmpl.tutor.Screenshotter", e);
107-
}
115+
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
116+
throw new Error("WARNING: Could not load screenshot feature from org.rascalmpl.tutor.Screenshotter", e);
117+
}
108118
}
109119

110120
private static ISourceLocation inferProjectRoot(ISourceLocation member) {
@@ -148,112 +158,83 @@ private String javaCompilerPathAsString(IList javaCompilerPath) {
148158
return b.toString();
149159
}
150160

151-
152-
public void reset() {
153-
try {
154-
// make sure previously unterminated commands are cleared up
155-
repl.handleInput("", new HashMap<>(), new HashMap<>());
156-
}
157-
catch (InterruptedException e) {
158-
// nothing needed
159-
}
160-
repl.cleanEnvironment();
161-
shellStandardOutput.reset();
162-
shellErrorOutput.reset();
163-
}
164161

165-
public String getPrompt() {
166-
return repl.getPrompt();
162+
public void reset() {
163+
interpreter.cancelRunningCommandRequested();
164+
interpreter.cleanEnvironment();
165+
outPrinter.flush();
166+
outWriter.getBuffer().setLength(0);
167+
errPrinter.flush();
168+
errWriter.getBuffer().setLength(0);
167169
}
168170

169171
public Map<String, String> eval(String line) throws InterruptedException, IOException {
170-
Map<String, InputStream> output = new HashMap<>();
171172
Map<String, String> result = new HashMap<>();
172-
Map<String, String> metadata = new HashMap<>();
173-
174-
repl.handleInput(line, output, metadata);
175-
176-
for (String mimeType : output.keySet()) {
177-
InputStream content = output.get(mimeType);
178-
179-
if (mimeType.startsWith("text/plain")) {
180-
result.put(mimeType, Prelude.consumeInputStream(new InputStreamReader(content, StandardCharsets.UTF_8)));
173+
try {
174+
var replResult = interpreter.handleInput(line);
175+
if (replResult instanceof IErrorCommandOutput) {
176+
((IErrorCommandOutput)replResult).asPlain().write(errPrinter, true);
181177
}
182-
else {
183-
result.put(mimeType, uuencode(content));
178+
else if (replResult instanceof IImageCommandOutput) {
179+
var img = ((IImageCommandOutput)replResult).asImage();
180+
result.put(img.mimeType(), uuencode(img));
184181
}
185-
186-
if (metadata.get("url") != null && screenshot != null) {
182+
else if (replResult instanceof IWebContentOutput) {
183+
var webResult = (IWebContentOutput)replResult;
187184
try {
188-
// load the page
189-
String pngImage = screenshot.takeScreenshotAsBase64PNG(metadata.get("url"));
185+
String pngImage = screenshot.takeScreenshotAsBase64PNG(webResult.webUri().toASCIIString());
190186

191187
if (!pngImage.isEmpty()) {
192188
result.put("application/rascal+screenshot", pngImage);
193189
}
194-
195190
}
196191
catch (Throwable e) {
197-
shellErrorOutput.write(e.getMessage().getBytes("UTF-8"));
192+
errPrinter.write(e.getMessage());
198193
}
199-
}
200-
}
201-
202-
result.put("application/rascal+stdout", getPrintedOutput());
203-
result.put("application/rascal+stderr", getErrorOutput());
204-
205-
return result;
206-
}
207-
208-
public String uuencode(InputStream content) throws IOException {
209-
int BUFFER_SIZE = 3 * 512;
210-
Base64.Encoder encoder = Base64.getEncoder();
211-
212-
try (BufferedInputStream in = new BufferedInputStream(content, BUFFER_SIZE); ) {
213-
StringBuilder result = new StringBuilder();
214-
byte[] chunk = new byte[BUFFER_SIZE];
215-
int len = 0;
216-
217-
// read multiples of 3 until not possible anymore
218-
while ( (len = in.read(chunk)) == BUFFER_SIZE ) {
219-
result.append( encoder.encodeToString(chunk) );
220194
}
221-
222-
// read final chunk which is not a multiple of 3
223-
if ( len > 0 ) {
224-
chunk = Arrays.copyOf(chunk,len);
225-
result.append( encoder.encodeToString(chunk) );
195+
// we ignore IAnsiCommandOutput, as we know that we cannot render that.
196+
// else if (replResult instanceof IAnsiCommandOutput) {}
197+
else if (replResult != null) {
198+
var txt = new StringWriter();
199+
var txtPrinter = new PrintWriter(txt, false);
200+
replResult.asPlain().write(txtPrinter, true);
201+
txtPrinter.flush();
202+
result.put("text/plain", txt.toString());
226203
}
227-
228-
return result.toString();
204+
else {
205+
result.put("text/plain", "ok\n");
206+
}
207+
} catch (ParseError pe) {
208+
ReadEvalPrintDialogMessages.parseErrorMessage(errPrinter, line, interpreter.promptRootLocation().getScheme(), pe, new StandardTextWriter(true));
209+
}
210+
catch (StopREPLException e1) {
211+
errWriter.write("Quiting REPL");
212+
} finally {
213+
result.put("application/rascal+stdout", getPrintedOutput());
214+
result.put("application/rascal+stderr", getErrorOutput());
229215
}
216+
return result;
230217
}
231218

232-
public boolean isStatementComplete(String line){
233-
return repl.isStatementComplete(line);
219+
private String uuencode(IBinaryOutputPrinter content) throws IOException {
220+
var result = new ByteArrayOutputStream();
221+
try (var wrapped = Base64.getEncoder().wrap(result)) {
222+
content.write(wrapped);
223+
}
224+
return result.toString(StandardCharsets.ISO_8859_1); // help java recognize the compact strings can be used
234225
}
235226

236-
private String getPrintedOutput() throws UnsupportedEncodingException{
237-
try {
238-
repl.getOutputWriter().flush();
239-
String result = shellStandardOutput.toString(StandardCharsets.UTF_8.name());
240-
shellStandardOutput.reset();
241-
return result;
242-
}
243-
catch (UnsupportedEncodingException e) {
244-
return "";
245-
}
227+
private String getPrintedOutput(){
228+
outPrinter.flush();
229+
String result = outWriter.toString();
230+
outWriter.getBuffer().setLength(0);
231+
return result;
246232
}
247233

248234
private String getErrorOutput() {
249-
try {
250-
repl.getErrorWriter().flush();
251-
String result = shellErrorOutput.toString(StandardCharsets.UTF_8.name());
252-
shellErrorOutput.reset();
253-
return result;
254-
}
255-
catch (UnsupportedEncodingException e) {
256-
return "";
257-
}
235+
errPrinter.flush();
236+
String result = errWriter.toString();
237+
errWriter.getBuffer().setLength(0);
238+
return result;
258239
}
259240
}

src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutor.rsc

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ This was created to implement documentation pages with example REPL runs.
1212
data CommandExecutor
1313
= executor(
1414
PathConfig pcfg,
15-
str () prompt,
1615
void () reset,
1716
map[str mimeType, str content] (str command) eval
1817
);
@@ -42,23 +41,13 @@ java CommandExecutor createExecutor(PathConfig pcfg);
4241
test bool executorSmokeTest() {
4342
exec = createExecutor(pathConfig());
4443

45-
if (exec.prompt() != "rascal\>") {
46-
return false;
47-
}
48-
4944
output = exec.eval("import IO;");
5045

5146
if (output["text/plain"] != "ok\n") {
5247
return false;
5348
}
5449

55-
exec.eval("println(\"haai\"");
56-
57-
if (exec.prompt() != "\>\>\>\>\>\>\>") {
58-
return false;
59-
}
60-
61-
output = exec.eval(")");
50+
exec.eval("println(\"haai\")");
6251

6352
if (output["application/rascal+stdout"] != "haai\n") {
6453
return false;

src/org/rascalmpl/tutor/lang/rascal/tutor/repl/TutorCommandExecutorCreator.java

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,13 @@
3333
*/
3434
public class TutorCommandExecutorCreator {
3535
private final IRascalValueFactory vf;
36-
private final Type promptType;
3736
private final Type resetType;
3837
private final Type evalType;
3938
private final Type execConstructor;
4039

4140
public TutorCommandExecutorCreator(IRascalValueFactory vf, TypeFactory tf, TypeStore ts) {
4241
this.vf = vf;
43-
promptType = tf.functionType(tf.stringType(), tf.tupleEmpty(), tf.tupleEmpty());
44-
resetType = tf.functionType(tf.voidType(), tf.tupleEmpty(), tf.tupleEmpty());
42+
resetType = tf.functionType(tf.voidType(), tf.tupleEmpty(), tf.tupleEmpty());
4543
evalType = tf.functionType(tf.mapType(tf.stringType(), tf.stringType()), tf.tupleType(tf.stringType()), tf.tupleEmpty());
4644
execConstructor = ts.lookupConstructor(ts.lookupAbstractDataType("CommandExecutor"), "executor").iterator().next();
4745
}
@@ -52,7 +50,6 @@ public IConstructor createExecutor(IConstructor pathConfigCons) {
5250
TutorCommandExecutor repl = new TutorCommandExecutor(pcfg);
5351
return vf.constructor(execConstructor,
5452
pathConfigCons,
55-
prompt(repl),
5653
reset(repl),
5754
eval(repl)
5855
);
@@ -62,12 +59,6 @@ public IConstructor createExecutor(IConstructor pathConfigCons) {
6259
}
6360
}
6461

65-
IFunction prompt(TutorCommandExecutor exec) {
66-
return vf.function(promptType, (args,kwargs) -> {
67-
return vf.string(exec.getPrompt());
68-
});
69-
}
70-
7162
IFunction reset(TutorCommandExecutor exec) {
7263
return vf.function(resetType, (args, kwargs) -> {
7364
exec.reset();

0 commit comments

Comments
 (0)