package org.rascalmpl.eclipse.pages;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.ui.progress.UIJob;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.env.Environment;
import org.rascalmpl.interpreter.env.ModuleEnvironment;
import org.rascalmpl.interpreter.result.IRascalResult;
import org.rascalmpl.interpreter.result.Result;
import org.rascalmpl.interpreter.utils.LimitedResultWriter.IOLimitReachedException;
import org.rascalmpl.parser.ASTBuilder;
import org.rascalmpl.repl.LimitedLineWriter;
import org.rascalmpl.repl.LimitedWriter;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.IListWriter;
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.io.StandardTextWriter;
import io.usethesource.vallang.type.Type;
import org.rascalmpl.values.uptr.ITree;
import org.rascalmpl.values.uptr.RascalValueFactory;
import org.rascalmpl.values.uptr.TreeAdapter;
import io.usethesource.impulse.editor.UniversalEditor;
import io.usethesource.impulse.parser.IModelListener;
import io.usethesource.impulse.parser.IParseController;
import io.usethesource.impulse.services.IEditorService;
public class EvalAndPatch implements IModelListener, IEditorService {
private final static int LINE_LIMIT = 200;
private final static int CHAR_LIMIT = LINE_LIMIT * 20;
public EvalAndPatch() {
// TODO Auto-generated constructor stub
}
private String printResult(IRascalResult result) throws IOException {
if (result == null) {
return "";
}
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
IValue value = result.getValue();
if (value == null) {
return "";
}
Type type = result.getType();
StandardTextWriter indentedPrettyPrinter = new StandardTextWriter();
if (type.isAbstractData() && type.isSubtypeOf(RascalValueFactory.Tree)) {
out.print(type.toString());
out.print(": ");
// we first unparse the tree
out.print("`");
TreeAdapter.yield((IConstructor) result.getValue(), true, out);
out.println("`");
// write parse tree out one a single line for reference
out.print("Tree: ");
StandardTextWriter singleLinePrettyPrinter = new StandardTextWriter(false);
try (Writer wrt = new LimitedWriter(out, CHAR_LIMIT)) {
singleLinePrettyPrinter.write(value, wrt);
}
catch (IOLimitReachedException e) {
// ignore since this is what we wanted
}
} else {
out.print(type.toString());
out.print(": ");
// limit both the lines and the characters
try (Writer wrt = new LimitedWriter(new LimitedLineWriter(out, LINE_LIMIT), CHAR_LIMIT)) {
indentedPrettyPrinter.write(value, wrt);
}
catch (IOLimitReachedException e) {
// ignore since this is what we wanted
}
}
out.flush();
return sw.toString();
}
private IList evalCommands(IValueFactory values, IList commands, ISourceLocation loc, Evaluator eval) {
StringWriter out = new StringWriter();
StringWriter err = new StringWriter();
IListWriter result = values.listWriter();
int outOffset = 0;
int errOffset = 0;
for (IValue v : commands) {
ITree cmd = (ITree)v;
if (TreeAdapter.getConstructorName(cmd) != null && TreeAdapter.getConstructorName(cmd).equals("output")) {
continue;
}
String errOut = "";
boolean exc = false;
Result<IValue> x = null;
PrintWriter stdout = eval.getStdOut();
PrintWriter stderr = eval.getStdErr();
try {
eval.overrideDefaultWriters(new PrintWriter(out), new PrintWriter(err));
x = new ASTBuilder().buildValue(cmd).interpret(eval);
} catch (Throwable e) {
errOut = err.getBuffer().substring(errOffset);
errOffset += errOut.length();
errOut += e.getMessage();
exc = true;
}
finally {
eval.overrideDefaultWriters(stdout, stderr);
}
String output = out.getBuffer().substring(outOffset);
outOffset += output.length();
if (!exc) {
errOut += err.getBuffer().substring(errOffset);
errOffset += errOut.length();
}
String s;
try {
s = printResult(x);
ITuple tuple = values.tuple(values.string(s), values.string(output), values.string(errOut));
result.append(tuple);
} catch (IOException e) {
continue;
}
}
IList results = result.done();
return results;
}
private String resultSource(IValueFactory vf, ITuple output, boolean[] addedSpace) {
addedSpace[0] = false;
IString val = (IString) output.get(0);
IString out = (IString) output.get(1);
IString err = (IString) output.get(2);
if (val.length() == 0 && out.length() == 0 && err.length() == 0) {
return "";
}
String code = "";
if (val.length() > 0) {
String x = val.getValue();
if (x.contains("origin=")) {
x = x.substring(0, x.indexOf(":"));
}
if (x.contains("`") && x.contains("Tree: ")) {
x = x.substring(0, x.indexOf("Tree: "));
}
code += " ⇨ " + x.replaceAll("\n", " ") + "\n";
addedSpace[0] = true;
}
if (out.length() > 0) {
if (!code.endsWith("\n")) {
code += "\n";
}
String txt = out.getValue().replaceAll("\n", "\n≫ ");
int ind = txt.lastIndexOf("\n≫ ");
if (ind == txt.length() - 3) {
txt = txt.substring(0, ind) + txt.substring(ind + 2, txt.length());
}
code += "≫ " + txt;
}
if (err.length() > 0 ) {
if (!code.endsWith("\n")) {
code += "\n";
}
String x = err.getValue();
if (x.contains("")) {
x = x.substring(0, x.indexOf(""));
}
code += "⚠ " + x.trim().replaceAll("\n", "\n⚠ ");
}
return code;
}
private IList patch(IValueFactory vf, IList args /* non ast */, IList results) {
IListWriter patch = vf.listWriter();
int delta = 0; // maintain where we are in the results list.
boolean[] addedSpace = new boolean[1]; // whether a leading space was added as part of output.
boolean change = false;
for (int i = 0; i < args.length(); i++) {
if (i % 2 == 0) { // a non-layout node
if (!(TreeAdapter.getConstructorName((ITree) args.get(i)).equals("output"))) { // a proper command
String src = resultSource(vf, (ITuple) results.get((i / 2) - delta), addedSpace);
// collect all subsequent layout and outputs following
// the current command to determine whether the computed
// output is different from the output in the previous run.
String old = "";
int j = i + 2;
while (j < args.length() && TreeAdapter.getConstructorName((ITree) args.get(j)).equals("output")) {
// TODO: do not eliminate comments!
old += TreeAdapter.yield((IConstructor) args.get(j - 1)) + TreeAdapter.yield((IConstructor) args.get(j));
j += 2;
}
// if there's a change in output, add a tuple
// to the patch.
if (!src.isEmpty() && !old.trim().equals(src.trim())) {
ISourceLocation org = TreeAdapter.getLocation((ITree) args.get(i));
int at = org.getOffset() + org.getLength();
ISourceLocation l = vf.sourceLocation(org, at, 0); // insert
patch.append(vf.tuple(l, vf.string(src)));
change = true;
}
else {
// signal to following iterations that there was no change.
change = false;
}
}
else {
if (change) {
// only remove previous output nodes if there was a change
patch.append(vf.tuple(TreeAdapter.getLocation((ITree) args.get(i)), vf.string("")));
}
// output commands are not evaluated by evalCommands above;
// delta maintains the difference for indexing.
delta += 1;
}
}
else {
String l = TreeAdapter.yield((IConstructor) args.get(i));
if (addedSpace[0] && change && l.startsWith(" ")) {
// if a leading space was added in the case of changed output,
// remove it here. Otherwise leave the layout unchanged.
ISourceLocation org = TreeAdapter.getLocation((ITree) args.get(i));
org = vf.sourceLocation(org, org.getOffset(), 1);
patch.append(vf.tuple(org, vf.string("")));
addedSpace[0] = false;
}
}
}
return patch.done();
}
class Job extends UIJob {
private IList patch;
private IDocument doc;
public Job(IList patch, IDocument doc) {
super("updating editor");
this.patch = patch;
this.doc = doc;
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
DocumentRewriteSession session = ((IDocumentExtension4) doc)
.startRewriteSession(DocumentRewriteSessionType.STRICTLY_SEQUENTIAL);
try {
int offset = 0;
for (IValue v : patch) {
ITuple subst = (ITuple) v;
ISourceLocation loc = (ISourceLocation) subst.get(0);
IString txt = (IString) subst.get(1);
doc.replace(loc.getOffset() + offset, loc.getLength(), txt.getValue());
offset += txt.length() - loc.getLength();
}
String lastChar = doc.get(doc.getLength() - 1, 1);
if (!lastChar.equals("\n")) {
doc.replace(doc.getLength(), 0, "\n");
}
} catch (UnsupportedOperationException e) {
e.printStackTrace();
return Status.CANCEL_STATUS;
} catch (BadLocationException e) {
e.printStackTrace();
return Status.CANCEL_STATUS;
} finally {
((IDocumentExtension4) doc).stopRewriteSession(session);
}
return Status.OK_STATUS;
}
}
@Override
public String getName() {
return "EvalAndPatch";
}
@Override
public void setEditor(UniversalEditor editor) {
// unused
}
@Override
public void update(IParseController parseController, IProgressMonitor monitor) {
ITree pt = (ITree) parseController.getCurrentAst();
if (pt == null) {
return;
}
IList commands = TreeAdapter.getASTArgs((ITree) TreeAdapter.getASTArgs(TreeAdapter.getArg(pt, "top")).get(0));
Evaluator evaluator = ((ParseController)parseController).getEvaluator();
IList results = null;
synchronized (evaluator) {
Environment env = evaluator.getCurrentEnvt();
try {
evaluator.setCurrentEnvt(new ModuleEnvironment("Scrapbook", evaluator.getHeap()));
results = evalCommands(evaluator.getValueFactory(), commands, TreeAdapter.getLocation(pt), evaluator);
}
finally {
evaluator.setCurrentEnvt(env);
}
}
if (results == null) {
return;
}
IList patch = patch(evaluator.getValueFactory(), TreeAdapter.getArgs((ITree) TreeAdapter.getASTArgs(TreeAdapter.getArg(pt, "top")).get(0)), results);
if (patch.isEmpty()) {
return;
}
Job job = new Job(patch, parseController.getDocument());
job.schedule();
}
}