diff --git a/.vscode/launch.json b/.vscode/launch.json index aa506d012ad..6f7385915ee 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,14 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "type": "java", + "name": "RascalShell2", + "request": "launch", + "mainClass": "org.rascalmpl.shell.RascalShell2", + "projectName": "rascal", + "console": "integratedTerminal" + }, { "type": "java", "name": "Launch DocRunner", @@ -38,7 +46,7 @@ "request": "launch", "mainClass": "org.rascalmpl.shell.RascalShell", "projectName": "rascal", - "cwd" : "${workspaceFolder}/../rascal-tutor", + "cwd": "${workspaceFolder}/../rascal-tutor", "vmArgs": "-Xss80m -Xmx2g -ea" }, { @@ -47,7 +55,7 @@ "request": "attach", "projectName": "rascal", "hostName": "localhost", - "port": 9001 + "port": 9213 }, { "type": "java", diff --git a/pom.xml b/pom.xml index 01c9f75078a..56d23205047 100644 --- a/pom.xml +++ b/pom.xml @@ -33,6 +33,7 @@ 2 11 0.28.4 + 3.27.0 @@ -332,10 +333,6 @@ org.fusesource.jansi.internal.* - - jline - org.rascalmpl.jline - @@ -464,10 +461,35 @@ gson 2.10.1 - - jline - jline - 2.14.6 + + org.jline + jline-reader + ${jline.version} + + + org.jline + jline-terminal + ${jline.version} + + + org.jline + jline-terminal-jni + ${jline.version} + + + org.jline + jline-style + ${jline.version} + + + org.jline + jline-console + ${jline.version} + + + org.jline + jansi-core + ${jline.version} org.yaml diff --git a/src/org/rascalmpl/checker/StaticChecker.java b/src/org/rascalmpl/checker/StaticChecker.java deleted file mode 100644 index acb755fa842..00000000000 --- a/src/org/rascalmpl/checker/StaticChecker.java +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2009-2013 CWI - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * * Jurgen J. Vinju - Jurgen.Vinju@cwi.nl - CWI - * * Mark Hills - Mark.Hills@cwi.nl (CWI) - * * Arnold Lankamp - Arnold.Lankamp@cwi.nl -*******************************************************************************/ -package org.rascalmpl.checker; - -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; - -import org.rascalmpl.debug.IRascalMonitor; -import org.rascalmpl.exceptions.ImplementationError; -import org.rascalmpl.interpreter.Configuration; -import org.rascalmpl.interpreter.Evaluator; -import org.rascalmpl.interpreter.env.GlobalEnvironment; -import org.rascalmpl.interpreter.env.ModuleEnvironment; -import org.rascalmpl.interpreter.load.StandardLibraryContributor; -import org.rascalmpl.parser.gtd.exception.ParseError; -import org.rascalmpl.uri.URIUtil; -import org.rascalmpl.values.ValueFactoryFactory; -import org.rascalmpl.values.parsetrees.ITree; - -import io.usethesource.vallang.ISourceLocation; -import io.usethesource.vallang.IValue; -import io.usethesource.vallang.IValueFactory; -import io.usethesource.vallang.type.Type; -import io.usethesource.vallang.type.TypeStore; - -public class StaticChecker { - private final Evaluator eval; - public static final String TYPECHECKER = "typecheckTree"; - private boolean checkerEnabled; - private boolean initialized; - private boolean loaded; - private Type pathConfigConstructor = null; - - public StaticChecker(OutputStream stderr, OutputStream stdout) { - GlobalEnvironment heap = new GlobalEnvironment(); - ModuleEnvironment root = heap.addModule(new ModuleEnvironment("$staticchecker$", heap)); - eval = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, stderr, stdout, root, heap, IRascalMonitor.buildConsoleMonitor(System.in, System.out)); - eval.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); - checkerEnabled = false; - initialized = false; - loaded = false; - } - - private IValue eval(IRascalMonitor monitor, String cmd) { - try { - return eval.eval(monitor, cmd, URIUtil.rootLocation("checker")).getValue(); - } catch (ParseError pe) { - throw new ImplementationError("syntax error in static checker modules", pe); - } - } - - public synchronized void load(IRascalMonitor monitor) { - eval(monitor, "import lang::rascal::types::CheckTypes;"); - eval(monitor, "import util::Reflective;"); - TypeStore ts = eval.getHeap().getModule("util::Reflective").getStore(); - pathConfigConstructor = ts.lookupConstructor(ts.lookupAbstractDataType("PathConfig"), "pathConfig").iterator().next(); - loaded = true; - } - - public void init() { - initialized = true; - } - - public Configuration getConfiguration() { - return eval.getConfiguration(); - } - - public boolean isInitialized() { - return initialized; - } - - public synchronized ITree checkModule(IRascalMonitor monitor, ISourceLocation module) { - if (checkerEnabled) { - return (ITree) eval.call(monitor, "check", module, getPathConfig()); - } - return null; - } - - private IValue getPathConfig() { - assert pathConfigConstructor != null; - IValueFactory vf = ValueFactoryFactory.getValueFactory(); - Map kwArgs = new HashMap<>(); - kwArgs.put("srcPath", vf.list(eval.getRascalResolver().collect().toArray(new IValue[0]))); - // default args for the rest - return vf.constructor(pathConfigConstructor, new IValue[0], kwArgs); - } - - public synchronized void disableChecker() { - checkerEnabled = false; - } - - public synchronized void enableChecker(IRascalMonitor monitor) { - if (!loaded) { - load(monitor); - } - checkerEnabled = true; - } - - public boolean isCheckerEnabled() { - return checkerEnabled; - } - - public void addRascalSearchPath(ISourceLocation uri) { - eval.addRascalSearchPath(uri); - } - - public void addClassLoader(ClassLoader classLoader) { - eval.addClassLoader(classLoader); - } - - public Evaluator getEvaluator() { - return eval; - } -} diff --git a/src/org/rascalmpl/debug/IRascalMonitor.java b/src/org/rascalmpl/debug/IRascalMonitor.java index 06452422f98..add6dd439df 100644 --- a/src/org/rascalmpl/debug/IRascalMonitor.java +++ b/src/org/rascalmpl/debug/IRascalMonitor.java @@ -15,18 +15,21 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.Reader; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; + +import org.jline.terminal.Terminal; +import org.jline.utils.InfoCmp.Capability; import org.rascalmpl.interpreter.BatchProgressMonitor; import org.rascalmpl.interpreter.NullRascalMonitor; import org.rascalmpl.repl.TerminalProgressBarMonitor; import io.usethesource.vallang.ISourceLocation; -import jline.Terminal; -import jline.TerminalFactory; public interface IRascalMonitor { /** @@ -158,8 +161,8 @@ default void jobStep(String name, String message) { * and otherwise default to a dumn terminal console progress logger. * @return */ - public static IRascalMonitor buildConsoleMonitor(InputStream in, OutputStream out) { - return buildConsoleMonitor(in, out, inBatchMode()); + public static IRascalMonitor buildConsoleMonitor(Terminal term) { + return buildConsoleMonitor(term, inBatchMode()); } public static boolean inBatchMode() { @@ -168,12 +171,11 @@ public static boolean inBatchMode() { ; } - public static IRascalMonitor buildConsoleMonitor(InputStream in, OutputStream out, boolean batchMode) { - Terminal terminal = TerminalFactory.get(); + public static IRascalMonitor buildConsoleMonitor(Terminal terminal, boolean batchMode) { - return !batchMode && terminal.isAnsiSupported() - ? new TerminalProgressBarMonitor(out, in, terminal) - : new BatchProgressMonitor(new PrintStream(out)) + return !batchMode && TerminalProgressBarMonitor.shouldWorkIn(terminal) + ? new TerminalProgressBarMonitor(terminal) + : new BatchProgressMonitor(terminal.writer()) ; } diff --git a/src/org/rascalmpl/interpreter/BatchProgressMonitor.java b/src/org/rascalmpl/interpreter/BatchProgressMonitor.java index 82793b67d80..43e3768d2c8 100644 --- a/src/org/rascalmpl/interpreter/BatchProgressMonitor.java +++ b/src/org/rascalmpl/interpreter/BatchProgressMonitor.java @@ -24,11 +24,15 @@ public class BatchProgressMonitor implements IRascalMonitor { PrintWriter out; public BatchProgressMonitor() { - this.out = new PrintWriter(System.err); + this(new PrintWriter(System.err, true)); } public BatchProgressMonitor(PrintStream out) { - this.out = new PrintWriter(out); + this(new PrintWriter(out)); + } + + public BatchProgressMonitor(PrintWriter out) { + this.out = out; } @Override diff --git a/src/org/rascalmpl/interpreter/Evaluator.java b/src/org/rascalmpl/interpreter/Evaluator.java index 5183ce362df..2950bb1c607 100755 --- a/src/org/rascalmpl/interpreter/Evaluator.java +++ b/src/org/rascalmpl/interpreter/Evaluator.java @@ -22,16 +22,11 @@ import static org.rascalmpl.semantics.dynamic.Import.parseFragments; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; import java.io.StringReader; import java.net.URI; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -41,9 +36,10 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.SortedMap; import java.util.SortedSet; import java.util.Stack; -import java.util.TreeSet; +import java.util.TreeMap; import java.util.concurrent.CopyOnWriteArrayList; import org.rascalmpl.ast.AbstractAST; @@ -195,17 +191,13 @@ public void decCallNesting() { private final List classLoaders; // sharable if frozen private final ModuleEnvironment rootScope; // sharable if frozen - private final OutputStream defStderr; - private final OutputStream defStdout; private final PrintWriter defOutWriter; private final PrintWriter defErrWriter; - private final InputStream defInput; + private final Reader defInput; - private OutputStream curStderr = null; - private OutputStream curStdout = null; private PrintWriter curOutWriter = null; private PrintWriter curErrWriter = null; - private InputStream curInput = null; + private Reader curInput = null; /** * Probably not sharable @@ -232,26 +224,26 @@ public void decCallNesting() { private static final Object dummy = new Object(); /** - * Promotes the monitor to the outputstream automatically if so required. + * Promotes the monitor to the PrintWriter automatically if so required. */ - public Evaluator(IValueFactory f, InputStream input, OutputStream stderr, OutputStream stdout, IRascalMonitor monitor, ModuleEnvironment scope, GlobalEnvironment heap) { - this(f, input, stderr, monitor instanceof OutputStream ? (OutputStream) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); + public Evaluator(IValueFactory f, Reader input, PrintWriter stderr, PrintWriter stdout, IRascalMonitor monitor, ModuleEnvironment scope, GlobalEnvironment heap) { + this(f, input, stderr, monitor instanceof PrintWriter ? (PrintWriter) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); } /** * If your monitor should wrap stdout (like TerminalProgressBarMonitor) then you can use this constructor. */ - public Evaluator(IValueFactory f, InputStream input, OutputStream stderr, M monitor, ModuleEnvironment scope, GlobalEnvironment heap) { + public Evaluator(IValueFactory f, Reader input, PrintWriter stderr, M monitor, ModuleEnvironment scope, GlobalEnvironment heap) { this(f, input, stderr, monitor, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); setMonitor(monitor); } - public Evaluator(IValueFactory f, InputStream input, OutputStream stderr, OutputStream stdout, ModuleEnvironment scope, GlobalEnvironment heap, IRascalMonitor monitor) { - this(f, input, stderr, monitor instanceof OutputStream ? (OutputStream) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); + public Evaluator(IValueFactory f, Reader input, PrintWriter stderr, PrintWriter stdout, ModuleEnvironment scope, GlobalEnvironment heap, IRascalMonitor monitor) { + this(f, input, stderr, monitor instanceof PrintWriter ? (PrintWriter) monitor : stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); setMonitor(monitor); } - public Evaluator(IValueFactory vf, InputStream input, OutputStream stderr, OutputStream stdout, ModuleEnvironment scope, GlobalEnvironment heap, List classLoaders, RascalSearchPath rascalPathResolver) { + public Evaluator(IValueFactory vf, Reader input, PrintWriter stderr, PrintWriter stdout, ModuleEnvironment scope, GlobalEnvironment heap, List classLoaders, RascalSearchPath rascalPathResolver) { super(); this.vf = new RascalFunctionValueFactory(this); @@ -265,10 +257,8 @@ public Evaluator(IValueFactory vf, InputStream input, OutputStream stderr, Outpu this.rascalPathResolver = rascalPathResolver; this.resolverRegistry = rascalPathResolver.getRegistry(); this.defInput = input; - this.defStderr = stderr; - this.defStdout = stdout; - this.defErrWriter = wrapWriter(stderr, true); - this.defOutWriter = wrapWriter(stdout, false); + this.defErrWriter = stderr; + this.defOutWriter = stdout; this.constructorDeclaredListeners = new HashMap(); this.suspendTriggerListeners = new CopyOnWriteArrayList(); @@ -285,10 +275,6 @@ public Evaluator(IValueFactory vf, InputStream input, OutputStream stderr, Outpu setEventTrigger(AbstractInterpreterEventTrigger.newNullEventTrigger()); } - private static PrintWriter wrapWriter(OutputStream out, boolean autoFlush) { - return new PrintWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8), autoFlush); - } - private Evaluator(Evaluator source, ModuleEnvironment scope) { super(); @@ -307,8 +293,6 @@ private Evaluator(Evaluator source, ModuleEnvironment scope) { this.javaBridge = new JavaBridge(classLoaders, vf, config); this.rascalPathResolver = source.rascalPathResolver; this.resolverRegistry = source.resolverRegistry; - this.defStderr = source.defStderr; - this.defStdout = source.defStdout; this.defInput = source.defInput; this.defErrWriter = source.defErrWriter; this.defOutWriter = source.defOutWriter; @@ -398,29 +382,20 @@ public List getClassLoaders() { return Collections.unmodifiableList(classLoaders); } - @Override + @Override public ModuleEnvironment __getRootScope() { return rootScope; } - @Override - public OutputStream getStdOut() { - return curStdout == null ? defStdout : curStdout; - } - - @Override - public PrintWriter getOutPrinter() { - return curOutWriter == null ? defOutWriter : curOutWriter; - } - @Override - public PrintWriter getErrorPrinter() { - return curErrWriter == null ? defErrWriter : curErrWriter; + public PrintWriter getStdOut() { + return curOutWriter == null ? defOutWriter : curOutWriter; } + @Override - public InputStream getInput() { + public Reader getInput() { return curInput == null ? defInput : curInput; } @@ -469,9 +444,9 @@ public boolean isInterrupted() { return interrupt; } - @Override - public OutputStream getStdErr() { - return curStderr == null ? defStderr : curStderr; + @Override + public PrintWriter getStdErr() { + return curErrWriter == null ? defErrWriter : curErrWriter; } public void setTestResultListener(ITestResultListener l) { @@ -629,7 +604,7 @@ else if (main.hasKeywordArguments() && main.getArity() == 0) { } } catch (MatchFailed e) { - getOutPrinter().println("Main function should either have a list[str] as a single parameter like so: \'void main(list[str] args)\', or a set of keyword parameters with defaults like so: \'void main(bool myOption=false, str input=\"\")\'"); + getStdOut().println("Main function should either have a list[str] as a single parameter like so: \'void main(list[str] args)\', or a set of keyword parameters with defaults like so: \'void main(bool myOption=false, str input=\"\")\'"); return null; } finally { @@ -831,7 +806,7 @@ public ParserGenerator getParserGenerator() { synchronized (self) { if (parserGenerator == null) { - parserGenerator = new ParserGenerator(getMonitor(), (monitor instanceof TerminalProgressBarMonitor) ? (OutputStream) getMonitor() : getStdErr(), classLoaders, getValueFactory(), config); + parserGenerator = new ParserGenerator(getMonitor(), (monitor instanceof TerminalProgressBarMonitor) ? (PrintWriter) getMonitor() : getStdErr(), classLoaders, getValueFactory(), config); } } } @@ -1220,13 +1195,13 @@ public Set reloadModules(IRascalMonitor monitor, Set names, ISou } private void reloadModules(IRascalMonitor monitor, Set names, ISourceLocation errorLocation, boolean recurseToExtending, Set affectedModules) { - SaveWarningsMonitor wrapped = new SaveWarningsMonitor(monitor, getErrorPrinter()); + SaveWarningsMonitor wrapped = new SaveWarningsMonitor(monitor, getStdErr()); IRascalMonitor old = setMonitor(wrapped); try { Set onHeap = new HashSet<>(); Set extendingModules = new HashSet<>(); - PrintWriter errStream = getErrorPrinter(); + PrintWriter errStream = getStdErr(); for (String mod : names) { if (heap.existsModule(mod)) { @@ -1513,7 +1488,7 @@ public boolean runTests(IRascalMonitor monitor) { IRascalMonitor old = setMonitor(monitor); try { final boolean[] allOk = new boolean[] { true }; - final ITestResultListener l = testReporter != null ? testReporter : new DefaultTestResultListener(getOutPrinter()); + final ITestResultListener l = testReporter != null ? testReporter : new DefaultTestResultListener(getStdErr()); new TestEvaluator(this, new ITestResultListener() { @@ -1578,17 +1553,13 @@ public IRascalMonitor getMonitor() { return new NullRascalMonitor(); } - public void overrideDefaultWriters(InputStream newInput, OutputStream newStdOut, OutputStream newStdErr) { - this.curStdout = newStdOut; - this.curStderr = newStdErr; + public void overrideDefaultWriters(Reader newInput, PrintWriter newStdOut, PrintWriter newStdErr) { this.curInput = newInput; - this.curErrWriter = wrapWriter(newStdErr, true); - this.curOutWriter = wrapWriter(newStdOut, false); + this.curErrWriter = newStdErr; + this.curOutWriter = newStdOut; } public void revertToDefaultWriters() { - this.curStderr = null; - this.curStdout = null; this.curInput = null; if (curOutWriter != null) { curOutWriter.flush(); @@ -1707,13 +1678,13 @@ public TraversalEvaluator __popTraversalEvaluator() { } @Override - public Collection completePartialIdentifier(String qualifier, String partialIdentifier) { + public Map completePartialIdentifier(String qualifier, String partialIdentifier) { if (partialIdentifier.startsWith("\\")) { partialIdentifier = partialIdentifier.substring(1); } String partialModuleName = qualifier + "::" + partialIdentifier; - SortedSet result = new TreeSet<>(new Comparator() { + SortedMap result = new TreeMap<>(new Comparator() { @Override public int compare(String a, String b) { if (a.charAt(0) == '\\') { @@ -1735,7 +1706,7 @@ public int compare(String a, String b) { return result; } - private void addCompletionsForModule(String qualifier, String partialIdentifier, String partialModuleName, SortedSet result, ModuleEnvironment env, boolean skipPrivate) { + private void addCompletionsForModule(String qualifier, String partialIdentifier, String partialModuleName, SortedMap result, ModuleEnvironment env, boolean skipPrivate) { for (Pair> p : env.getFunctions()) { for (AbstractFunction f : p.getSecond()) { String module = ((ModuleEnvironment)f.getEnv()).getName(); @@ -1745,7 +1716,7 @@ private void addCompletionsForModule(String qualifier, String partialIdentifier, } if (module.startsWith(qualifier)) { - addIt(result, p.getFirst(), qualifier.isEmpty() ? "" : module, module.startsWith(partialModuleName) ? "" : partialIdentifier); + addIt(result, "function", p.getFirst(), qualifier.isEmpty() ? "" : module, module.startsWith(partialModuleName) ? "" : partialIdentifier); } } } @@ -1755,30 +1726,30 @@ private void addCompletionsForModule(String qualifier, String partialIdentifier, if (skipPrivate && env.isNamePrivate(entry.getKey())) { continue; } - addIt(result, entry.getKey(), qualifier, partialIdentifier); + addIt(result, "variable", entry.getKey(), qualifier, partialIdentifier); } for (Type t: env.getAbstractDatatypes()) { if (inQualifiedModule) { - addIt(result, t.getName(), qualifier, partialIdentifier); + addIt(result, "ADT", t.getName(), qualifier, partialIdentifier); } } for (Type t: env.getAliases()) { - addIt(result, t.getName(), qualifier, partialIdentifier); + addIt(result, "alias", t.getName(), qualifier, partialIdentifier); } } if (qualifier.isEmpty()) { Map> annos = env.getAnnotations(); for (Type t: annos.keySet()) { for (String k: annos.get(t).keySet()) { - addIt(result, k, "", partialIdentifier); + addIt(result, "annotation", k, "", partialIdentifier); } } } } - private static void addIt(SortedSet result, String v, String qualifier, String originalTerm) { + private static void addIt(SortedMap result, String category, String v, String qualifier, String originalTerm) { if (v.startsWith(originalTerm) && !v.equals(originalTerm)) { if (v.contains("-")) { v = "\\" + v; @@ -1786,7 +1757,7 @@ private static void addIt(SortedSet result, String v, String qualifier, if (!qualifier.isEmpty() && !v.startsWith(qualifier)) { v = qualifier + "::" + v; } - result.add(v); + result.put(v, category); } } @@ -1983,4 +1954,10 @@ public void showMessage(IConstructor message) { } } } + + + public void overwritePrintWriter(PrintWriter outWriter, PrintWriter errWriter) { + this.curOutWriter = outWriter; + this.curErrWriter = errWriter; + } } diff --git a/src/org/rascalmpl/interpreter/IEvaluatorContext.java b/src/org/rascalmpl/interpreter/IEvaluatorContext.java index 49db817c31b..25562ff4b68 100644 --- a/src/org/rascalmpl/interpreter/IEvaluatorContext.java +++ b/src/org/rascalmpl/interpreter/IEvaluatorContext.java @@ -17,10 +17,10 @@ *******************************************************************************/ package org.rascalmpl.interpreter; -import java.io.InputStream; -import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Reader; import java.util.Collection; +import java.util.Map; import java.util.Stack; import org.rascalmpl.ast.AbstractAST; @@ -41,13 +41,10 @@ public interface IEvaluatorContext extends IRascalMonitor { public StackTrace getStackTrace(); /** for standard IO */ - public PrintWriter getOutPrinter(); - public PrintWriter getErrorPrinter(); - - public OutputStream getStdOut(); - public OutputStream getStdErr(); + public PrintWriter getStdOut(); + public PrintWriter getStdErr(); - public InputStream getInput(); + public Reader getInput(); /** for "internal use" */ public IEvaluator> getEvaluator(); @@ -77,5 +74,6 @@ public interface IEvaluatorContext extends IRascalMonitor { public Stack getAccumulators(); - public Collection completePartialIdentifier(String qualifier, String partialIdentifier); + /** @return identifiers and their category (variable, function, etc) */ + public Map completePartialIdentifier(String qualifier, String partialIdentifier); } diff --git a/src/org/rascalmpl/interpreter/TestEvaluator.java b/src/org/rascalmpl/interpreter/TestEvaluator.java index 726921716c2..cab11b6268f 100644 --- a/src/org/rascalmpl/interpreter/TestEvaluator.java +++ b/src/org/rascalmpl/interpreter/TestEvaluator.java @@ -130,8 +130,8 @@ private void runTests(ModuleEnvironment env, List tests) { } }, env.getRoot().getStore(), tries, maxDepth, maxWidth); - eval.getOutPrinter().flush(); - eval.getErrorPrinter().flush(); + eval.getStdOut().flush(); + eval.getStdErr().flush(); if (!result.succeeded()) { StringWriter sw = new StringWriter(); @@ -147,8 +147,8 @@ private void runTests(ModuleEnvironment env, List tests) { testResultListener.report(false, test.getName(), test.getAst().getLocation(), e.getMessage(), e); } - eval.getOutPrinter().flush(); - eval.getErrorPrinter().flush(); + eval.getStdOut().flush(); + eval.getStdErr().flush(); } testResultListener.done(); diff --git a/src/org/rascalmpl/interpreter/result/AbstractFunction.java b/src/org/rascalmpl/interpreter/result/AbstractFunction.java index 326a1a19725..e458f680a10 100644 --- a/src/org/rascalmpl/interpreter/result/AbstractFunction.java +++ b/src/org/rascalmpl/interpreter/result/AbstractFunction.java @@ -293,8 +293,8 @@ protected void printStartTrace(IValue[] actuals) { b.append("call >"); printNesting(b); printHeader(b, actuals); - eval.getOutPrinter().println(b.toString()); - eval.getOutPrinter().flush(); + eval.getStdOut().println(b.toString()); + eval.getStdOut().flush(); eval.incCallNesting(); } @@ -318,8 +318,8 @@ protected void printExcept(Throwable e) { b.append(": "); String msg = e.getMessage(); b.append(msg == null ? e.getClass().getSimpleName() : msg); - eval.getOutPrinter().println(b.toString()); - eval.getOutPrinter().flush(); + eval.getStdOut().println(b.toString()); + eval.getStdOut().flush(); } } @@ -336,8 +336,8 @@ protected void printEndTrace(IValue result) { b.append(strval(result)); } - eval.getOutPrinter().println(b); - eval.getOutPrinter().flush(); + eval.getStdOut().println(b); + eval.getStdOut().flush(); } } diff --git a/src/org/rascalmpl/interpreter/result/JavaMethod.java b/src/org/rascalmpl/interpreter/result/JavaMethod.java index 10530bda752..1ce4fd9c8c6 100644 --- a/src/org/rascalmpl/interpreter/result/JavaMethod.java +++ b/src/org/rascalmpl/interpreter/result/JavaMethod.java @@ -91,7 +91,7 @@ private JavaMethod(IEvaluator> eval, Type staticType, Type dynami super(func, eval, staticType, dynamicType, getFormals(func), Names.name(func.getSignature().getName()), isDefault, isTest, varargs, env); this.javaBridge = javaBridge; this.hasReflectiveAccess = hasReflectiveAccess(func); - this.instance = javaBridge.getJavaClassInstance(func, eval.getMonitor(), env.getStore(), eval.getOutPrinter(), eval.getErrorPrinter(), eval.getStdOut(), eval.getStdErr(), eval.getInput(), eval); + this.instance = javaBridge.getJavaClassInstance(func, eval.getMonitor(), env.getStore(), eval.getStdOut(), eval.getStdErr(), eval.getInput(), eval); this.method = javaBridge.lookupJavaMethod(eval, func, env, hasReflectiveAccess); } diff --git a/src/org/rascalmpl/interpreter/utils/JavaBridge.java b/src/org/rascalmpl/interpreter/utils/JavaBridge.java index 201703578f1..bfe2049dd53 100644 --- a/src/org/rascalmpl/interpreter/utils/JavaBridge.java +++ b/src/org/rascalmpl/interpreter/utils/JavaBridge.java @@ -19,6 +19,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Reader; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -375,14 +376,11 @@ public Class visitFunction(Type type) throws RuntimeException { } } - public synchronized Object getJavaClassInstance(FunctionDeclaration func, IRascalMonitor monitor, TypeStore store, PrintWriter out, PrintWriter err, OutputStream rawOut, OutputStream rawErr, InputStream in, IEvaluatorContext ctx) { + public synchronized Object getJavaClassInstance(FunctionDeclaration func, IRascalMonitor monitor, TypeStore store, PrintWriter out, PrintWriter err, Reader in, IEvaluatorContext ctx) { String className = getClassName(func); PrintWriter[] outputs = new PrintWriter[] { out, err }; int writers = 0; - - OutputStream[] rawOutputs = new OutputStream[] { rawOut, rawErr }; - int rawWriters = 0; try { for(ClassLoader loader : loaders){ @@ -425,10 +423,7 @@ else if (formals[i].isAssignableFrom(TypeFactory.class)) { else if (formals[i].isAssignableFrom(PrintWriter.class)) { args[i] = outputs[writers++ % 2]; } - else if (formals[i].isAssignableFrom(OutputStream.class)) { - args[i] = rawOutputs[rawWriters++ %2]; - } - else if (formals[i].isAssignableFrom(InputStream.class)) { + else if (formals[i].isAssignableFrom(Reader.class)) { args[i] = in; } else if (formals[i].isAssignableFrom(IRascalMonitor.class)) { diff --git a/src/org/rascalmpl/interpreter/utils/Profiler.java b/src/org/rascalmpl/interpreter/utils/Profiler.java index 40eba860655..0be9d9c7323 100644 --- a/src/org/rascalmpl/interpreter/utils/Profiler.java +++ b/src/org/rascalmpl/interpreter/utils/Profiler.java @@ -160,7 +160,7 @@ public IList getProfileData(){ public void report() { report("FRAMES", frame); - eval.getOutPrinter().println(); + eval.getStdOut().println(); report("ASTS", ast); } @@ -178,7 +178,7 @@ private void report(String title, Map data) { nTicks += e.getValue().getTicks(); } - PrintWriter out = eval.getOutPrinter(); + PrintWriter out = eval.getStdOut(); String nameFormat = "%" + maxName + "s"; out.printf(title + " PROFILE: %d data points, %d ticks, tick = %d milliSecs\n", ast.size(), nTicks, resolution); out.printf(nameFormat + "%8s%9s %s\n", " Scope", "Ticks", "%", "Source"); diff --git a/src/org/rascalmpl/library/util/Eval.java b/src/org/rascalmpl/library/util/Eval.java index 6da394f8621..dd2835f8f46 100644 --- a/src/org/rascalmpl/library/util/Eval.java +++ b/src/org/rascalmpl/library/util/Eval.java @@ -16,8 +16,8 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; import java.net.URISyntaxException; import java.nio.file.Paths; import java.util.HashMap; @@ -80,12 +80,12 @@ public class Eval { private final Type execConstructor; /* the following four fields are inherited by the configuration of nested evaluators */ - private final OutputStream stderr; - private final OutputStream stdout; - private final InputStream input; + private final PrintWriter stderr; + private final PrintWriter stdout; + private final Reader input; private final IDEServices services; - public Eval(IRascalValueFactory values, OutputStream out, OutputStream err, InputStream in, ClassLoader loader, IDEServices services, TypeStore ts) { + public Eval(IRascalValueFactory values, PrintWriter out, PrintWriter err, Reader in, ClassLoader loader, IDEServices services, TypeStore ts) { super(); this.values = values; this.tr = new TypeReifier(values); @@ -219,7 +219,7 @@ private static class RascalRuntime { private final Evaluator eval; private int duration = -1; - public RascalRuntime(PathConfig pcfg, InputStream input, OutputStream stderr, OutputStream stdout, IDEServices services) throws IOException, URISyntaxException{ + public RascalRuntime(PathConfig pcfg, Reader input, PrintWriter stderr, PrintWriter stdout, IDEServices services) throws IOException, URISyntaxException{ GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); diff --git a/src/org/rascalmpl/library/util/Reflective.java b/src/org/rascalmpl/library/util/Reflective.java index 44bb23ce3b1..371d60474bc 100644 --- a/src/org/rascalmpl/library/util/Reflective.java +++ b/src/org/rascalmpl/library/util/Reflective.java @@ -99,21 +99,23 @@ public IConstructor getProjectPathConfig(ISourceLocation projectRoot, IConstruct } } - IEvaluator getDefaultEvaluator(OutputStream stdout, OutputStream stderr) { + IEvaluator getDefaultEvaluator(PrintWriter stdout, PrintWriter stderr) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); - Evaluator evaluator = new Evaluator(vf, System.in, stderr, stdout, root, heap, monitor); + Evaluator evaluator = new Evaluator(vf, Reader.nullReader(), stderr, stdout, root, heap, monitor); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); return evaluator; } public IList evalCommands(IList commands, ISourceLocation loc) { - OutputStream out = new ByteArrayOutputStream(); - OutputStream err = new ByteArrayOutputStream(); + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + PrintWriter outStream = new PrintWriter(out); + PrintWriter errStream = new PrintWriter(err); IListWriter result = values.listWriter(); - IEvaluator evaluator = getDefaultEvaluator(out, err); + IEvaluator evaluator = getDefaultEvaluator(outStream, errStream); int outOffset = 0; int errOffset = 0; @@ -125,11 +127,16 @@ public IList evalCommands(IList commands, ISourceLocation loc) { x = evaluator.eval(evaluator.getMonitor(), ((IString)v).getValue(), loc); } catch (Throwable e) { + errStream.flush(); errOut = err.toString().substring(errOffset); errOffset += errOut.length(); errOut += e.getMessage(); exc = true; } + finally { + outStream.flush(); + errStream.flush(); + } String output = out.toString().substring(outOffset); outOffset += output.length(); if (!exc) { diff --git a/src/org/rascalmpl/library/util/TermREPL.java b/src/org/rascalmpl/library/util/TermREPL.java_disabled similarity index 94% rename from src/org/rascalmpl/library/util/TermREPL.java rename to src/org/rascalmpl/library/util/TermREPL.java_disabled index f149bb36340..d6728583314 100644 --- a/src/org/rascalmpl/library/util/TermREPL.java +++ b/src/org/rascalmpl/library/util/TermREPL.java_disabled @@ -6,6 +6,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Reader; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; @@ -13,6 +15,7 @@ import java.util.Map; import java.util.function.Function; + import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; @@ -42,17 +45,16 @@ import io.usethesource.vallang.IWithKeywordParameters; import io.usethesource.vallang.type.Type; import io.usethesource.vallang.type.TypeFactory; -import jline.TerminalFactory; public class TermREPL { private final IRascalValueFactory vf; private ILanguageProtocol lang; - private final OutputStream out; - private final OutputStream err; - private final InputStream in; + private final PrintWriter out; + private final PrintWriter err; + private final Reader in; - public TermREPL(IRascalValueFactory vf, OutputStream out, OutputStream err, InputStream in) { + public TermREPL(IRascalValueFactory vf, PrintWriter out, PrintWriter err, Reader in) { this.vf = vf; this.out = out; this.err = err; @@ -94,9 +96,9 @@ public ITuple newREPL(IConstructor repl, IString title, IString welcome, IString public static class TheREPL implements ILanguageProtocol { private final REPLContentServerManager contentManager = new REPLContentServerManager(); private final TypeFactory tf = TypeFactory.getInstance(); - private OutputStream stdout; - private OutputStream stderr; - private InputStream input; + private PrintWriter stdout; + private PrintWriter stderr; + private Reader input; private String currentPrompt; private String quit; private final AbstractFunction handler; @@ -105,7 +107,7 @@ public static class TheREPL implements ILanguageProtocol { private final AbstractFunction stacktrace; public TheREPL(IValueFactory vf, IString title, IString welcome, IString prompt, IString quit, ISourceLocation history, - IFunction handler, IFunction completor, IValue stacktrace, InputStream input, OutputStream stderr, OutputStream stdout) { + IFunction handler, IFunction completor, IValue stacktrace, Reader input, PrintWriter stderr, PrintWriter stdout) { this.vf = vf; this.input = input; this.stderr = stderr; @@ -145,7 +147,7 @@ public void stackTraceRequested() { } @Override - public void initialize(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services) { + public void initialize(Reader input, PrintWriter stdout, PrintWriter stderr, IDEServices services) { this.stdout = stdout; this.stderr = stderr; this.input = input; @@ -293,14 +295,9 @@ private IValue call(IFunction f, Type[] types, IValue[] args) { return f.call(args); } finally { - try { - stdout.flush(); - stderr.flush(); - eval.revertToDefaultWriters(); - } - catch (IOException e) { - // ignore - } + stdout.flush(); + stderr.flush(); + eval.revertToDefaultWriters(); } } } diff --git a/src/org/rascalmpl/parser/ParserGenerator.java b/src/org/rascalmpl/parser/ParserGenerator.java index 19b55ae06bb..514f917dcd1 100644 --- a/src/org/rascalmpl/parser/ParserGenerator.java +++ b/src/org/rascalmpl/parser/ParserGenerator.java @@ -17,6 +17,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; import java.util.List; import org.rascalmpl.debug.IRascalMonitor; @@ -52,10 +54,10 @@ public class ParserGenerator { private static final String packageName = "org.rascalmpl.java.parser.object"; private static final boolean debug = false; - public ParserGenerator(IRascalMonitor monitor, OutputStream out, List loaders, IValueFactory factory, Configuration config) { + public ParserGenerator(IRascalMonitor monitor, PrintWriter out, List loaders, IValueFactory factory, Configuration config) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment scope = new ModuleEnvironment("$parsergenerator$", heap); - this.evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, out, out, scope, heap, monitor); + this.evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), Reader.nullReader(), out, out, scope, heap, monitor); this.evaluator.getConfiguration().setRascalJavaClassPathProperty(config.getRascalJavaClassPathProperty()); this.evaluator.getConfiguration().setGeneratorProfiling(config.getGeneratorProfilingProperty()); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); @@ -200,7 +202,7 @@ public Class> getNewParser(IRascalMon finally { if (profiler != null) { profiler.pleaseStop(); - evaluator.getOutPrinter().println("PROFILE:"); + evaluator.getStdOut().println("PROFILE:"); profiler.report(); profiler = null; } diff --git a/src/org/rascalmpl/repl/BaseREPL.java b/src/org/rascalmpl/repl/BaseREPL.java_disabled old mode 100755 new mode 100644 similarity index 100% rename from src/org/rascalmpl/repl/BaseREPL.java rename to src/org/rascalmpl/repl/BaseREPL.java_disabled diff --git a/src/org/rascalmpl/repl/BaseREPL2.java b/src/org/rascalmpl/repl/BaseREPL2.java new file mode 100644 index 00000000000..49ba74016e1 --- /dev/null +++ b/src/org/rascalmpl/repl/BaseREPL2.java @@ -0,0 +1,207 @@ +package org.rascalmpl.repl; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.jline.reader.EndOfFileException; +import org.jline.reader.LineReader; +import org.jline.reader.LineReader.Option; +import org.jline.reader.LineReaderBuilder; +import org.jline.reader.UserInterruptException; +import org.jline.reader.impl.completer.AggregateCompleter; +import org.jline.reader.impl.history.DefaultHistory; +import org.jline.terminal.Terminal; +import org.jline.terminal.Terminal.Signal; +import org.jline.terminal.Terminal.SignalHandler; +import org.jline.utils.ShutdownHooks; + +public class BaseREPL2 { + + private final IREPLService replService; + private final Terminal term; + private final LineReader reader; + private volatile boolean keepRunning = true; + private final @MonotonicNonNull DefaultHistory history; + private String currentPrompt; + private static final String FALLBACK_MIME_TYPE = "text/plain"; + private static final String ANSI_MIME_TYPE = "text/x-ansi"; + private final boolean ansiSupported; + private final boolean unicodeSupported; + private final String mimeType; + + public BaseREPL2(IREPLService replService, Terminal term) { + this.replService = replService; + this.term = term; + + var reader = LineReaderBuilder.builder() + .appName(replService.name()) + .terminal(term) + .parser(replService.inputParser()) + ; + + if (replService.storeHistory()) { + reader.variable(LineReader.HISTORY_FILE, replService.historyFile()); + this.history = new DefaultHistory(); + reader.history(this.history); + ShutdownHooks.add(this.history::save); + } else { + this.history = null; + } + reader.option(Option.HISTORY_IGNORE_DUPS, replService.historyIgnoreDuplicates()); + reader.option(Option.DISABLE_EVENT_EXPANSION, true); // stop jline expending escaped characters in the input + + + if (replService.supportsCompletion()) { + reader.completer(new AggregateCompleter(replService.completers())); + } + + switch (term.getType()) { + case Terminal.TYPE_DUMB: + this.ansiSupported = false; + this.mimeType = FALLBACK_MIME_TYPE; + break; + case Terminal.TYPE_DUMB_COLOR: + this.ansiSupported = false; + this.mimeType = ANSI_MIME_TYPE; + break; + default: + this.ansiSupported = true; + this.mimeType = ANSI_MIME_TYPE; + break; + } + this.unicodeSupported = term.encoding().newEncoder().canEncode("💓"); + this.currentPrompt = replService.prompt(ansiSupported, unicodeSupported); + reader.variable(LineReader.SECONDARY_PROMPT_PATTERN, replService.parseErrorPrompt(ansiSupported, unicodeSupported)); + + this.reader = reader.build(); + + + + // todo: + // - ctrl + / support (might not be possible) + // - highlighting in the prompt? (future work, as it also hurts other parts) + // - nested REPLs + // - support for html results + // - measure time + // - history? + } + + public void run() throws IOException { + try { + replService.connect(term); + var running = setupInterruptHandler(); + + while (keepRunning) { + try { + replService.flush(); + String line = reader.readLine(this.currentPrompt); + + if (line == null) { + // EOF + break; + } + running.set(true); + handleInput(line); + } + catch (UserInterruptException u) { + // only thrown while `readLine` is active + reader.printAbove(">>>>>>> Interrupted"); + term.flush(); + } + finally { + running.set(false); + } + } + } + catch (InterruptedException _e) { + // closing the runner + } + catch (EndOfFileException e) { + // user pressed ctrl+d or the terminal :quit command was given + // so exit cleanly + replService.errorWriter().println("Quiting REPL"); + } + catch (Throwable e) { + + var err = replService.errorWriter(); + if (err.checkError()) { + err = new PrintWriter(System.err, false); + } + + err.println("Unexpected (uncaught) exception, closing the REPL: "); + err.print(e.toString()); + e.printStackTrace(err); + + err.flush(); + + throw e; + } + finally { + try { + replService.flush(); + } catch (Throwable _t) { /* ignore */ } + term.flush(); + if (this.history != null) { + ShutdownHooks.remove(this.history::save); + this.history.save(); + } + } + } + + /** + * Queue a command (separated by newlines) to be "entered" + * No support for multi-line input + */ + public void queueCommand(String command) { + reader.addCommandsInBuffer(Arrays.asList(command.split("[\\n\\r]"))); + } + + private AtomicBoolean setupInterruptHandler() { + var running = new AtomicBoolean(false); + var original = new AtomicReference(null); + original.set(term.handle(Signal.INT, (s) -> { + if (running.get()) { + try { + replService.handleInterrupt(); + } + catch (InterruptedException e) { + return; + } + } + else { + var fallback = original.get(); + if (fallback != null) { + fallback.handle(s); + } + } + })); + + return running; + } + + + private void handleInput(String line) throws InterruptedException { + var result = new HashMap(); + var meta = new HashMap(); + replService.handleInput(line, result, meta); + writeResult(result); + } + + private void writeResult(HashMap result) { + var writer = result.get(this.mimeType); + if (writer == null) { + writer = result.get(FALLBACK_MIME_TYPE); + } + if (writer == null) { + replService.outputWriter().println("Ok"); + } + else { + writer.write(replService.outputWriter()); + } + } +} diff --git a/src/org/rascalmpl/repl/BaseRascalREPL.java b/src/org/rascalmpl/repl/BaseRascalREPL.java index 24a3177a7e3..14caabb1e5d 100755 --- a/src/org/rascalmpl/repl/BaseRascalREPL.java +++ b/src/org/rascalmpl/repl/BaseRascalREPL.java @@ -26,6 +26,7 @@ import org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages; import org.rascalmpl.interpreter.utils.StringUtils; import org.rascalmpl.interpreter.utils.StringUtils.OffsetLengthTerm; +import org.rascalmpl.repl.completers.RascalCommandCompletion; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.values.RascalValueFactory; diff --git a/src/org/rascalmpl/repl/ILanguageProtocol.java b/src/org/rascalmpl/repl/ILanguageProtocol.java index 9a32ca88762..9da826d0389 100755 --- a/src/org/rascalmpl/repl/ILanguageProtocol.java +++ b/src/org/rascalmpl/repl/ILanguageProtocol.java @@ -24,7 +24,8 @@ package org.rascalmpl.repl; import java.io.InputStream; -import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; import java.util.Map; import org.rascalmpl.ideservices.IDEServices; @@ -37,7 +38,7 @@ public interface ILanguageProtocol { * @param stdout the output stream to write normal output to. * @param stderr the error stream to write error messages on, depending on the environment and options passed, will print in red. */ - void initialize(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services); + void initialize(Reader input, PrintWriter stdout, PrintWriter stderr, IDEServices services); /** * Will be called everytime a new prompt is printed. diff --git a/src/org/rascalmpl/repl/IOutputPrinter.java b/src/org/rascalmpl/repl/IOutputPrinter.java new file mode 100644 index 00000000000..6b055f852fb --- /dev/null +++ b/src/org/rascalmpl/repl/IOutputPrinter.java @@ -0,0 +1,23 @@ +package org.rascalmpl.repl; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; + +public interface IOutputPrinter { + void write(PrintWriter target); + + default Reader asReader() { + try (var result = new StringWriter()) { + try (var resultWriter = new PrintWriter(result)) { + write(resultWriter); + } + return new StringReader(result.toString()); + } + catch (IOException ex) { + throw new IllegalStateException("StringWriter close should never throw exception", ex); + } + } +} diff --git a/src/org/rascalmpl/repl/IREPLService.java b/src/org/rascalmpl/repl/IREPLService.java new file mode 100644 index 00000000000..5be73ad6c5e --- /dev/null +++ b/src/org/rascalmpl/repl/IREPLService.java @@ -0,0 +1,94 @@ +package org.rascalmpl.repl; + +import java.io.PrintWriter; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.jline.reader.Completer; +import org.jline.reader.Parser; +import org.jline.reader.impl.DefaultParser; +import org.jline.terminal.Terminal; + +public interface IREPLService { + + String MIME_PLAIN = "text/plain"; + String MIME_ANSI = "text/x-ansi"; + + /** + * Does this language support completion + * @return + */ + default boolean supportsCompletion() { + return false; + } + + /** + * Supply completers for this REPL. + * Note that a completor is only triggered on a per word basis, so you might want to overwrite {@see #completionParser()} + */ + default List completers() { + return Collections.emptyList(); + } + + /** + * This parser is respossible for multi-line support, as well as word splitting for completion. + */ + default Parser inputParser() { + return new DefaultParser(); + } + + + default boolean storeHistory() { + return false; + } + + default boolean historyIgnoreDuplicates() { + return true; + } + + default Path historyFile() { + throw new IllegalAccessError("Not implemented if storeHistory is false"); + } + + /** + * Name of the REPL, no ansi allowed + */ + default String name() { return "Rascal REPL"; } + + + /** + * Check if an input is valid, for multi-line support + */ + boolean isInputComplete(String input); + + + // todo see if we really need the meta-data + void handleInput(String input, Map output, Map metadata) throws InterruptedException; + + /** + * Will be called from a different thread then the one that called `handleInput` + */ + void handleInterrupt() throws InterruptedException; + + /** + * Default prompt + */ + String prompt(boolean ansiSupported, boolean unicodeSupported); + /** + * Continuation prompt + */ + String parseErrorPrompt(boolean ansiSupported, boolean unicodeSupported); + + void connect(Terminal term); + + PrintWriter errorWriter(); + PrintWriter outputWriter(); + + /** + * Flush the streams, will be triggered at the end of execution, and before showing the prompt. + */ + void flush(); + +} diff --git a/src/org/rascalmpl/repl/ItalicErrorWriter.java b/src/org/rascalmpl/repl/ItalicErrorWriter.java index b9a7340b3d1..a650a64e4ac 100644 --- a/src/org/rascalmpl/repl/ItalicErrorWriter.java +++ b/src/org/rascalmpl/repl/ItalicErrorWriter.java @@ -2,8 +2,9 @@ import java.io.Writer; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.Ansi.Attribute; +import org.jline.jansi.Ansi; +import org.jline.jansi.Ansi.Attribute; + public class ItalicErrorWriter extends WrappedFilterWriter { public ItalicErrorWriter(Writer out) { diff --git a/src/org/rascalmpl/repl/RascalCommandCompletion.java b/src/org/rascalmpl/repl/RascalCommandCompletion.java deleted file mode 100755 index 2ed269379d5..00000000000 --- a/src/org/rascalmpl/repl/RascalCommandCompletion.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.rascalmpl.repl; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.SortedSet; -import java.util.TreeSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import org.rascalmpl.interpreter.utils.StringUtils; -import org.rascalmpl.interpreter.utils.StringUtils.OffsetLengthTerm; - -public class RascalCommandCompletion { - private static final TreeSet COMMAND_KEYWORDS; - static { - COMMAND_KEYWORDS = new TreeSet<>(); - COMMAND_KEYWORDS.add("set"); - COMMAND_KEYWORDS.add("undeclare"); - COMMAND_KEYWORDS.add("help"); - COMMAND_KEYWORDS.add("edit"); - COMMAND_KEYWORDS.add("unimport"); - COMMAND_KEYWORDS.add("declarations"); - COMMAND_KEYWORDS.add("quit"); - COMMAND_KEYWORDS.add("history"); - COMMAND_KEYWORDS.add("test"); - COMMAND_KEYWORDS.add("modules"); - COMMAND_KEYWORDS.add("clear"); - } - - - private static final Pattern splitCommand = Pattern.compile("^[\\t ]*:(?[a-z]*)([\\t ]|$)"); - public static CompletionResult complete(String line, int cursor, SortedSet commandOptions, CompletionFunction completeIdentifier, CompletionFunction completeModule) { - assert line.trim().startsWith(":"); - Matcher m = splitCommand.matcher(line); - if (m.find()) { - String currentCommand = m.group("command"); - switch(currentCommand) { - case "set": { - OffsetLengthTerm identifier = StringUtils.findRascalIdentifierAtOffset(line, cursor); - if (identifier != null && identifier.offset > m.end("command")) { - Collection suggestions = commandOptions.stream() - .filter(s -> s.startsWith(identifier.term)) - .sorted() - .collect(Collectors.toList()); - if (suggestions != null && ! suggestions.isEmpty()) { - return new CompletionResult(identifier.offset, suggestions); - } - } - else if (line.trim().equals(":set")) { - return new CompletionResult(line.length(), commandOptions); - } - return null; - } - case "undeclare": return completeIdentifier.complete(line, cursor); - case "edit": - case "unimport": return completeModule.complete(line, line.length()); - default: { - if (COMMAND_KEYWORDS.contains(currentCommand)) { - return null; // nothing to complete after a full command - } - List result = null; - if (currentCommand.isEmpty()) { - result = new ArrayList<>(COMMAND_KEYWORDS); - } - else { - result = COMMAND_KEYWORDS.stream() - .filter(s -> s.startsWith(currentCommand)) - .collect(Collectors.toList()); - } - if (!result.isEmpty()) { - return new CompletionResult(m.start("command"), result); - } - } - } - } - return null; - } - -} diff --git a/src/org/rascalmpl/repl/RascalInterpreterREPL.java b/src/org/rascalmpl/repl/RascalInterpreterREPL.java_disabled similarity index 90% rename from src/org/rascalmpl/repl/RascalInterpreterREPL.java rename to src/org/rascalmpl/repl/RascalInterpreterREPL.java_disabled index 17febce23d5..2c06ecdfe83 100644 --- a/src/org/rascalmpl/repl/RascalInterpreterREPL.java +++ b/src/org/rascalmpl/repl/RascalInterpreterREPL.java_disabled @@ -10,6 +10,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Reader; import java.io.Writer; import java.net.URISyntaxException; import java.util.Collection; @@ -71,7 +72,7 @@ public boolean getMeasureCommandTime() { } @Override - public void initialize(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices ideServices) { + public void initialize(Reader input, PrintWriter stdout, PrintWriter stderr, IDEServices ideServices) { eval = constructEvaluator(input, stdout, stderr, ideServices); } @@ -79,7 +80,7 @@ public void initialize(InputStream input, OutputStream stdout, OutputStream stde @Override public PrintWriter getErrorWriter() { - return eval.getErrorPrinter(); + return eval.getStdErr(); } @Override @@ -89,7 +90,7 @@ public InputStream getInput() { @Override public PrintWriter getOutputWriter() { - return eval.getOutPrinter(); + return eval.getStdOut(); } @Override @@ -135,37 +136,37 @@ public IRascalResult evalStatement(String statement, String lastLine) throws Int duration = tm.duration(); } if (measureCommandTime) { - eval.getErrorPrinter().println("\nTime: " + duration + "ms"); + eval.getStdErr().println("\nTime: " + duration + "ms"); } return value; } catch (InterruptException ie) { - eval.getErrorPrinter().println("Interrupted"); + eval.getStdErr().println("Interrupted"); try { - ie.getRascalStackTrace().prettyPrintedString(eval.getErrorPrinter(), indentedPrettyPrinter); + ie.getRascalStackTrace().prettyPrintedString(eval.getStdErr(), indentedPrettyPrinter); } catch (IOException e) { } return null; } catch (ParseError pe) { - parseErrorMessage(eval.getErrorPrinter(), lastLine, "prompt", pe, indentedPrettyPrinter); + parseErrorMessage(eval.getStdErr(), lastLine, "prompt", pe, indentedPrettyPrinter); return null; } catch (StaticError e) { - staticErrorMessage(eval.getErrorPrinter(),e, indentedPrettyPrinter); + staticErrorMessage(eval.getStdErr(),e, indentedPrettyPrinter); return null; } catch (Throw e) { - throwMessage(eval.getErrorPrinter(),e, indentedPrettyPrinter); + throwMessage(eval.getStdErr(),e, indentedPrettyPrinter); return null; } catch (QuitException q) { - eval.getErrorPrinter().println("Quiting REPL"); + eval.getStdErr().println("Quiting REPL"); throw new InterruptedException(); } catch (Throwable e) { - throwableMessage(eval.getErrorPrinter(), e, eval.getStackTrace(), indentedPrettyPrinter); + throwableMessage(eval.getStdErr(), e, eval.getStackTrace(), indentedPrettyPrinter); return null; } } diff --git a/src/org/rascalmpl/repl/RascalReplServices.java b/src/org/rascalmpl/repl/RascalReplServices.java new file mode 100644 index 00000000000..c45f320a140 --- /dev/null +++ b/src/org/rascalmpl/repl/RascalReplServices.java @@ -0,0 +1,339 @@ +package org.rascalmpl.repl; + +import static org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages.parseErrorMessage; +import static org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages.staticErrorMessage; +import static org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages.throwMessage; +import static org.rascalmpl.interpreter.utils.ReadEvalPrintDialogMessages.throwableMessage; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringReader; +import java.io.Writer; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.Objects; +import java.util.TreeMap; +import java.util.function.Function; + +import org.jline.jansi.Ansi; +import org.jline.reader.Completer; +import org.jline.reader.EndOfFileException; +import org.jline.reader.Parser; +import org.jline.terminal.Terminal; +import org.jline.utils.InfoCmp.Capability; +import org.rascalmpl.exceptions.Throw; +import org.rascalmpl.interpreter.Configuration; +import org.rascalmpl.interpreter.Evaluator; +import org.rascalmpl.interpreter.NullRascalMonitor; +import org.rascalmpl.interpreter.control_exceptions.InterruptException; +import org.rascalmpl.interpreter.control_exceptions.QuitException; +import org.rascalmpl.interpreter.result.IRascalResult; +import org.rascalmpl.interpreter.result.Result; +import org.rascalmpl.interpreter.staticErrors.StaticError; +import org.rascalmpl.parser.gtd.exception.ParseError; +import org.rascalmpl.repl.completers.RascalCommandCompletion; +import org.rascalmpl.repl.completers.RascalIdentifierCompletion; +import org.rascalmpl.repl.completers.RascalModuleCompletion; +import org.rascalmpl.repl.completers.RascalKeywordCompletion; +import org.rascalmpl.repl.completers.RascalLocationCompletion; +import org.rascalmpl.repl.jline3.RascalLineParser; +import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.values.RascalValueFactory; +import org.rascalmpl.values.parsetrees.TreeAdapter; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.IString; +import io.usethesource.vallang.IValue; +import io.usethesource.vallang.io.StandardTextWriter; +import io.usethesource.vallang.type.Type; + +public class RascalReplServices implements IREPLService { + private final Function buildEvaluator; + private Evaluator eval; + private static final StandardTextWriter ansiIndentedPrinter = new ReplTextWriter(true); + private static final StandardTextWriter plainIndentedPrinter = new StandardTextWriter(true); + private final static int LINE_LIMIT = 200; + private final static int CHAR_LIMIT = LINE_LIMIT * 20; + private String newline = System.lineSeparator(); + + + public RascalReplServices(Function buildEvaluator) { + super(); + this.buildEvaluator = buildEvaluator; + } + + @Override + public void connect(Terminal term) { + if (eval != null) { + throw new IllegalStateException("REPL is already initialized"); + } + newline = term.getStringCapability(Capability.newline); + if (newline == null) { + newline = System.lineSeparator(); + } + this.eval = buildEvaluator.apply(term); + } + + private static final ISourceLocation PROMPT_LOCATION = URIUtil.rootLocation("prompt"); + + @Override + public Parser inputParser() { + return new RascalLineParser(prompt -> { + synchronized(eval) { + return eval.parseCommand(new NullRascalMonitor(), prompt, PROMPT_LOCATION); + } + }); + } + + @Override + public boolean isInputComplete(String input) { + throw new UnsupportedOperationException("Unimplemented method 'isInputComplete'"); + } + + + @Override + public void handleInput(String input, Map output, Map metadata) + throws InterruptedException { + synchronized(eval) { + Objects.requireNonNull(eval, "Not initialized yet"); + try { + Result value; + value = eval.eval(eval.getMonitor(), input, URIUtil.rootLocation("prompt")); + outputResult(output, value); + } + catch (InterruptException ex) { + reportError(output, (w, sw) -> { + w.println("Interrupted"); + ex.getRascalStackTrace().prettyPrintedString(w, sw); + }); + } + catch (ParseError pe) { + reportError(output, (w, sw) -> { + parseErrorMessage(w, input, "prompt", pe, sw); + }); + } + catch (StaticError e) { + reportError(output, (w, sw) -> { + staticErrorMessage(w, e, sw); + }); + } + catch (Throw e) { + reportError(output, (w, sw) -> { + throwMessage(w,e, sw); + }); + } + catch (QuitException q) { + reportError(output, (w, sw) -> { + w.println("Quiting REPL"); + }); + throw new EndOfFileException("Quiting REPL"); + } + catch (Throwable e) { + reportError(output, (w, sw) -> { + throwableMessage(w, e, eval.getStackTrace(), sw); + }); + } + } + } + + private void outputResult(Map output, IRascalResult result) { + if (result == null || result.getValue() == null) { + output.put(MIME_PLAIN, new StringOutputPrinter("ok", newline)); + return; + } + IValue value = result.getValue(); + Type type = result.getStaticType(); + + if (type.isSubtypeOf(RascalValueFactory.Content) && !type.isBottom()) { + output.put(MIME_PLAIN, new StringOutputPrinter("Serving content", newline)); + // TODO: serve content! + return; + } + + ThrowingWriter resultWriter; + if (type.isAbstractData() && type.isStrictSubtypeOf(RascalValueFactory.Tree) && !type.isBottom()) { + resultWriter = (w, sw) -> { + w.write("(" + type.toString() +") `"); + TreeAdapter.yield((IConstructor)value, sw == ansiIndentedPrinter, w); + w.write("`"); + }; + } + else if (type.isString()) { + resultWriter = (w, sw) -> { + // TODO: do something special for the reader version of IString, when that is released + // for now, we only support write + + try (Writer wrt = new LimitedWriter(new LimitedLineWriter(w, LINE_LIMIT), CHAR_LIMIT)) { + sw.write(value, wrt); + } + catch (/*IOLimitReachedException*/ RuntimeException e) { + // ignore since this is what we wanted + // if we catch IOLimitReachedException we get an IllegalArgument exception instead + // "Self-suppression not permitted" + } + w.println(); + w.println("---"); + try (Writer wrt = new LimitedWriter(new LimitedLineWriter(w, LINE_LIMIT), CHAR_LIMIT)) { + ((IString) value).write(wrt); + } + catch (/*IOLimitReachedException*/ RuntimeException e) { + // ignore since this is what we wanted + // if we catch IOLimitReachedException we get an IllegalArgument exception instead + // "Self-suppression not permitted" + } + w.println(); + w.print("---"); + }; + } + else { + resultWriter = (w, sw) -> { + try (Writer wrt = new LimitedWriter(new LimitedLineWriter(w, LINE_LIMIT), CHAR_LIMIT)) { + sw.write(value, wrt); + } + catch (/*IOLimitReachedException*/ RuntimeException e) { + // ignore since this is what we wanted + // if we catch IOLimitReachedException we get an IllegalArgument exception instead + // "Self-suppression not permitted" + } + }; + } + + ThrowingWriter typePrefixed = (w, sw) -> { + w.write(type.toString()); + w.write(": "); + resultWriter.write(w, sw); + w.println(); + }; + + output.put(MIME_PLAIN, new ExceptionPrinter(typePrefixed, plainIndentedPrinter)); + output.put(MIME_ANSI, new ExceptionPrinter(typePrefixed, ansiIndentedPrinter)); + + } + + private static void reportError(Map output, ThrowingWriter writer) { + output.put(MIME_PLAIN, new ExceptionPrinter(writer, plainIndentedPrinter)); + output.put(MIME_ANSI, new ExceptionPrinter(writer, ansiIndentedPrinter)); + } + + @FunctionalInterface + private static interface ThrowingWriter { + void write(PrintWriter writer, StandardTextWriter prettyPrinter) throws IOException; + } + + private static class ExceptionPrinter implements IOutputPrinter { + private final ThrowingWriter internalWriter; + private final StandardTextWriter prettyPrinter; + + public ExceptionPrinter(ThrowingWriter internalWriter, StandardTextWriter prettyPrinter) { + this.internalWriter = internalWriter; + this.prettyPrinter = prettyPrinter; + } + + @Override + public void write(PrintWriter target) { + try { + internalWriter.write(target, prettyPrinter); + } + catch (IOException e) { + target.println("Internal failure: printing exception failed with:"); + target.println(e.toString()); + e.printStackTrace(target); + } + } + } + + private static class StringOutputPrinter implements IOutputPrinter { + private final String value; + private final String newline; + + public StringOutputPrinter(String value, String newline) { + this.value = value; + this.newline = newline; + } + + @Override + public void write(PrintWriter target) { + target.println(value); + } + + @Override + public Reader asReader() { + return new StringReader(value + newline); + } + } + + @Override + public void handleInterrupt() throws InterruptedException { + eval.interrupt(); + } + + @Override + public String prompt(boolean ansiSupported, boolean unicodeSupported) { + if (ansiSupported) { + return Ansi.ansi().reset().bold() + "rascal>" + Ansi.ansi().reset(); + } + return "rascal>"; + } + + @Override + public String parseErrorPrompt(boolean ansiSupported, boolean unicodeSupported) { + String errorPrompt = (unicodeSupported ? "│" : "|") + "%N %P>"; + if (ansiSupported) { + return Ansi.ansi().reset().bold() + errorPrompt + Ansi.ansi().reset(); + } + return errorPrompt; + } + + + @Override + public PrintWriter errorWriter() { + return eval.getStdErr(); + } + + @Override + public PrintWriter outputWriter() { + return eval.getStdOut(); + } + + @Override + public void flush() { + // TODO figure out why this function is called? + eval.getStdErr().flush(); + eval.getStdOut().flush(); + } + + private static final NavigableMap commandLineOptions = new TreeMap<>(); + static { + commandLineOptions.put(Configuration.GENERATOR_PROFILING_PROPERTY.substring("rascal.".length()), "enable sampling profiler for generator"); + commandLineOptions.put(Configuration.PROFILING_PROPERTY.substring("rascal.".length()), "enable sampling profiler" ); + commandLineOptions.put(Configuration.ERRORS_PROPERTY.substring("rascal.".length()), "print raw java errors"); + commandLineOptions.put(Configuration.TRACING_PROPERTY.substring("rascal.".length()), "trace all function calls (warning: a lot of output will be generated)"); + } + + @Override + public boolean supportsCompletion() { + return true; + } + + @Override + public List completers() { + var result = new ArrayList(); + var moduleCompleter = new RascalModuleCompletion(m -> eval.getRascalResolver().listModuleEntries(m)); + var idCompleter = new RascalIdentifierCompletion((q, i) -> eval.completePartialIdentifier(q, i)); + result.add(new RascalCommandCompletion( + commandLineOptions, + idCompleter::completePartialIdentifier, + (s, c) -> moduleCompleter.completeModuleNames(s, c, false) + )); + result.add(moduleCompleter); + result.add(idCompleter); + result.add(new RascalKeywordCompletion()); + result.add(new RascalLocationCompletion()); + return result; + } + +} diff --git a/src/org/rascalmpl/repl/RedErrorWriter.java b/src/org/rascalmpl/repl/RedErrorWriter.java index 92dac60c344..b2300a3787d 100644 --- a/src/org/rascalmpl/repl/RedErrorWriter.java +++ b/src/org/rascalmpl/repl/RedErrorWriter.java @@ -2,9 +2,10 @@ import java.io.Writer; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.Ansi.Attribute; -import org.fusesource.jansi.Ansi.Color; +import org.jline.jansi.Ansi; +import org.jline.jansi.Ansi.Attribute; +import org.jline.jansi.Ansi.Color; + public class RedErrorWriter extends WrappedFilterWriter { public RedErrorWriter(Writer out) { diff --git a/src/org/rascalmpl/repl/ReplTextWriter.java b/src/org/rascalmpl/repl/ReplTextWriter.java index 475c0f0520f..647680fb2e8 100644 --- a/src/org/rascalmpl/repl/ReplTextWriter.java +++ b/src/org/rascalmpl/repl/ReplTextWriter.java @@ -3,9 +3,9 @@ import java.io.IOException; import java.io.StringWriter; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.Ansi.Attribute; -import org.fusesource.jansi.Ansi.Color; +import org.jline.jansi.Ansi; +import org.jline.jansi.Ansi.Attribute; +import org.jline.jansi.Ansi.Color; import org.rascalmpl.interpreter.utils.LimitedResultWriter; import io.usethesource.vallang.ISourceLocation; diff --git a/src/org/rascalmpl/repl/SourceLocationHistory.java b/src/org/rascalmpl/repl/SourceLocationHistory.java_disabled similarity index 89% rename from src/org/rascalmpl/repl/SourceLocationHistory.java rename to src/org/rascalmpl/repl/SourceLocationHistory.java_disabled index 8964873ba40..eb14ea04d84 100644 --- a/src/org/rascalmpl/repl/SourceLocationHistory.java +++ b/src/org/rascalmpl/repl/SourceLocationHistory.java_disabled @@ -9,14 +9,14 @@ import java.io.PrintStream; import java.io.Reader; +import org.jline.reader.History.Entry; +import org.jline.utils.Log; import org.rascalmpl.uri.URIResolverRegistry; + import io.usethesource.vallang.ISourceLocation; -import jline.console.history.MemoryHistory; -import jline.console.history.PersistentHistory; -import jline.internal.Log; -public class SourceLocationHistory extends MemoryHistory implements PersistentHistory { +public class SourceLocationHistory extends History implements PersistentHistory { private static final URIResolverRegistry reg = URIResolverRegistry.getInstance(); private final ISourceLocation loc; diff --git a/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java b/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java index f2c349fb2cf..0174bdf67a8 100644 --- a/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java +++ b/src/org/rascalmpl/repl/TerminalProgressBarMonitor.java @@ -1,12 +1,7 @@ package org.rascalmpl.repl; -import java.io.FilterOutputStream; -import java.io.FilterWriter; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.io.PrintWriter; -import java.nio.charset.Charset; import java.time.Duration; import java.time.Instant; import java.util.ArrayList; @@ -14,10 +9,11 @@ import java.util.LinkedList; import java.util.List; +import org.jline.terminal.Terminal; +import org.jline.utils.InfoCmp.Capability; import org.rascalmpl.debug.IRascalMonitor; + import io.usethesource.vallang.ISourceLocation; -import jline.Terminal; -import jline.internal.Configuration; /** * The terminal progress bar monitor wraps the standard output stream to be able to monitor @@ -31,7 +27,7 @@ * This class only works correctly if the actual _raw_ output stream of the terminal is wrapped * with an object of this class. */ -public class TerminalProgressBarMonitor extends FilterOutputStream implements IRascalMonitor { +public class TerminalProgressBarMonitor extends PrintWriter implements IRascalMonitor { /** * We administrate an ordered list of named bars, which will be printed from * top to bottom just above the next prompt. @@ -45,12 +41,6 @@ public class TerminalProgressBarMonitor extends FilterOutputStream implements IR */ private List unfinishedLines = new ArrayList<>(3); - /** - * This writer is there to help with the encoding to what the terminal needs. It writes directly to the - * underlying stream. - */ - private final PrintWriter writer; - /** * The entire width in character columns of the current terminal. Resizes everytime when we start * the first job. @@ -65,66 +55,68 @@ public class TerminalProgressBarMonitor extends FilterOutputStream implements IR /**x * Will make everything slow, but easier to spot mistakes */ - private final boolean debug = false; + private static final boolean debug = false; /** * Used to get updates to the width of the terminal */ private final Terminal tm; - private final String encoding; + public static boolean shouldWorkIn(Terminal tm) { + return "\r".equals(tm.getStringCapability(Capability.carriage_return)) + && tm.getNumericCapability(Capability.columns) != null + && tm.getNumericCapability(Capability.lines) != null + && ANSI.supportsCapabilities(tm); + } + @SuppressWarnings("resource") - public TerminalProgressBarMonitor(OutputStream out, InputStream in, Terminal tm) { - super(out); + public TerminalProgressBarMonitor(Terminal tm) { + super(debug ? new AlwaysFlushAlwaysShowCursor(tm.writer()) : tm.writer()); - this.encoding = Configuration.getEncoding(); this.tm = tm; - PrintWriter theWriter = new PrintWriter(out, true, Charset.forName(encoding)); - this.writer = debug ? new PrintWriter(new AlwaysFlushAlwaysShowCursor(theWriter)) : theWriter; this.lineWidth = tm.getWidth(); - this.unicodeEnabled = ANSI.isUTF8enabled(theWriter, in); + this.unicodeEnabled = tm.encoding().newEncoder().canEncode(new ProgressBar("", 1).clocks[0]); - assert tm.isSupported() && tm.isAnsiSupported(): "interactive progress bar needs a working ANSI terminal"; assert out.getClass() != TerminalProgressBarMonitor.class : "accidentally wrapping the wrapper."; } /** * Use this for debugging terminal cursor movements, step by step. */ - private static class AlwaysFlushAlwaysShowCursor extends FilterWriter { + private static class AlwaysFlushAlwaysShowCursor extends PrintWriter { public AlwaysFlushAlwaysShowCursor(PrintWriter out) { super(out); } @Override - public void write(int c) throws IOException { - out.write(c); - out.write(ANSI.showCursor()); - out.flush(); + public void write(int c) { + super.write(c); + super.write(ANSI.showCursor()); + super.flush(); } @Override - public void write(char[] cbuf, int off, int len) throws IOException { - out.write(cbuf, off, len); - out.write(ANSI.showCursor()); - out.flush(); + public void write(char[] cbuf, int off, int len) { + super.write(cbuf, off, len); + super.write(ANSI.showCursor()); + super.flush(); } @Override - public void write(String str, int off, int len) throws IOException { - out.write(str, off, len); - out.write(ANSI.showCursor()); - out.flush(); + public void write(String str, int off, int len) { + super.write(str, off, len); + super.write(ANSI.showCursor()); + super.flush(); } } - private static class UnfinishedLine { + private class UnfinishedLine { final long threadId; private int curCapacity = 512; - private byte[] buffer = new byte[curCapacity]; + private char[] buffer = new char[curCapacity]; private int curEnd = 0; public UnfinishedLine() { @@ -137,7 +129,7 @@ public UnfinishedLine() { * * The resulting buffer nevers contain any newline character. */ - private void store(byte[] newInput, int offset, int len) { + private void store(char[] newInput, int offset, int len) { if (len == 0) { return; // fast exit } @@ -152,37 +144,37 @@ private void store(byte[] newInput, int offset, int len) { curEnd += len; } - public void write(byte[] n, OutputStream out) throws IOException { - write(n, 0, n.length, out); - } - /** * Main workhorse looks for newline characters in the new input. * - if there are newlines, than whatever is in the buffer can be flushed. * - all the characters up to the last new new line are flushed immediately. * - all the new characters after the last newline are buffered. */ - public void write(byte[] n, int offset, int len, OutputStream out) throws IOException { + public void write(char[] n, int offset, int len) { int lastNL = startOfLastLine(n, offset, len); if (lastNL == -1) { store(n, offset, len); } else { - flush(out); - out.write(n, offset, lastNL + 1); - out.flush(); + flush(); + rawWrite(n, offset, lastNL + 1); + rawFlush(); store(n, lastNL + 1, len - (lastNL + 1)); } } + public void write(String s, int offset, int len) { + write(s.toCharArray(), offset, len); + } + /** * This empties the current buffer onto the stream, * and resets the cursor. */ - private void flush(OutputStream out) throws IOException { + private void flush() { if (curEnd != 0) { - out.write(buffer, 0, curEnd); + rawWrite(buffer, 0, curEnd); curEnd = 0; } } @@ -191,14 +183,14 @@ private void flush(OutputStream out) throws IOException { * Prints whatever is the last line in the buffer, * and adds a newline. */ - public void flushLastLine(OutputStream out) throws IOException { + public void flushLastLine() { if (curEnd != 0) { - flush(out); - out.write('\n'); + flush(); + rawWrite('\n'); } } - private int startOfLastLine(byte[] buffer, int offset, int len) { + private int startOfLastLine(char[] buffer, int offset, int len) { for (int i = offset + len - 1; i >= offset; i--) { if (buffer[i] == '\n') { return i; @@ -209,6 +201,29 @@ private int startOfLastLine(byte[] buffer, int offset, int len) { } } + private void rawPrintln(String s) { + rawWrite(s + System.lineSeparator()); + } + private void rawWrite(String s) { + rawWrite(s, 0, s.length()); + } + + private void rawWrite(int c) { + super.write(c); + } + + private void rawWrite(char[] buf, int offset, int length) { + super.write(buf, offset, length); + } + + private void rawWrite(String buf, int offset, int length) { + super.write(buf, offset, length); + } + + private void rawFlush() { + super.flush(); + } + /** * Represents one currently running progress bar */ @@ -264,14 +279,14 @@ void update() { // to avoid flicker we only print if there is a new bar character to draw if (newWidth() != previousWidth) { stepper++; - writer.write(ANSI.moveUp(bars.size() - bars.indexOf(this))); + rawWrite(ANSI.moveUp(bars.size() - bars.indexOf(this))); write(); // this moves the cursor already one line down due to `println` int distance = bars.size() - bars.indexOf(this) - 1; if (distance > 0) { // ANSI will move 1 line even if the parameter is 0 - writer.write(ANSI.moveDown(distance)); + rawWrite(ANSI.moveDown(distance)); } - writer.flush(); + rawFlush(); } } @@ -323,7 +338,7 @@ void write() { return; // robustness against very small screens. At least don't throw bounds exceptions } else if (barWidth <= 3) { // we can print the clock for good measure - writer.println(clock); + rawPrintln(clock); return; } @@ -340,7 +355,7 @@ else if (barWidth <= 3) { // we can print the clock for good measure + " " ; - writer.println(line); // note this puts us one line down + rawPrintln(line); // note this puts us one line down } @Override @@ -370,39 +385,23 @@ public void done() { */ private void eraseBars() { if (!bars.isEmpty()) { - writer.write(ANSI.moveUp(bars.size())); - writer.write(ANSI.clearToEndOfScreen()); + rawWrite(ANSI.moveUp(bars.size())); + rawWrite(ANSI.clearToEndOfScreen()); } - writer.flush(); + rawFlush(); } /** * ANSI escape codes convenience functions */ private static class ANSI { - static boolean isUTF8enabled(PrintWriter writer, InputStream in) { - try { - int pos = getCursorPosition(writer, in); - // Japanese A (あ) is typically 3 bytes in most encodings, but should be less than 3 ANSI columns - // on screen if-and-only-if unicode is supported. - writer.write("あ"); - writer.flush(); - int newPos = getCursorPosition(writer, in); - int diff = newPos - pos; - - try { - return diff < 3; - } - finally { - while (--diff >= 0) { - writer.write(ANSI.delete()); - } - writer.flush(); - } - } - catch (IOException e) { - return false; + + private static boolean supportsCapabilities(Terminal tm) { + Integer cols = tm.getNumericCapability(Capability.max_colors); + if (cols == null || cols < 8) { + return false; } + return tm.getStringCapability(Capability.clear_screen) != null; } public static String grey8Background() { @@ -413,53 +412,10 @@ public static String brightWhiteForeground() { return "\u001B[97m"; } - static int getCursorPosition(PrintWriter writer, InputStream in) throws IOException { - writer.write(ANSI.printCursorPosition()); - writer.flush(); - - byte[] col = new byte[32]; - int len = in.read(col); - String echo; - - try { - echo = new String(col, 0, len, Configuration.getEncoding()); - } - catch (StringIndexOutOfBoundsException e) { - // this happens if there is some other input on stdin (for example a pipe) - // TODO: the input is now read and can't be processed again. - echo = ""; - } - - if (!echo.startsWith("\u001B[") || !echo.contains(";")) { - return -1; - } - - // terminal responds with ESC[n;mR, where n is the row and m is the column. - echo = echo.split(";")[1]; // take the column part - echo = echo.substring(0, echo.length() - 1); // remove the last R - return Integer.parseInt(echo); - } - - public static String delete() { - return "\u001B[D\u001B[K"; - } - static String moveUp(int n) { return "\u001B[" + n + "F"; } - static String overlined() { - return "\u001B[53m"; - } - - static String underlined() { - return "\u001B[4m"; - } - - public static String printCursorPosition() { - return "\u001B[6n"; - } - public static String noBackground() { return "\u001B[49m"; } @@ -468,10 +424,6 @@ public static String normal() { return "\u001B[0m"; } - public static String lightBackground() { - return "\u001B[48;5;250m"; - } - static String moveDown(int n) { return "\u001B[" + n + "E"; } @@ -497,7 +449,7 @@ private void printBars() { pb.write(); } - writer.flush(); + rawFlush(); } @@ -538,7 +490,7 @@ public synchronized void jobStart(String name, int workShare, int totalWork) { var pb = findBarByName(name); - writer.write(ANSI.hideCursor()); + rawWrite(ANSI.hideCursor()); if (pb == null) { eraseBars(); // to make room for the new bars @@ -552,8 +504,8 @@ public synchronized void jobStart(String name, int workShare, int totalWork) { pb.update(); } - writer.write(ANSI.showCursor()); - writer.flush(); + rawWrite(ANSI.showCursor()); + rawFlush(); } @Override @@ -561,11 +513,11 @@ public synchronized void jobStep(String name, String message, int workShare) { ProgressBar pb = findBarByName(name); if (pb != null) { - writer.write(ANSI.hideCursor()); + rawWrite(ANSI.hideCursor()); pb.worked(workShare, message); pb.update(); - writer.write(ANSI.showCursor()); - writer.flush(); + rawWrite(ANSI.showCursor()); + rawFlush(); } } @@ -573,7 +525,7 @@ public synchronized void jobStep(String name, String message, int workShare) { public synchronized int jobEnd(String name, boolean succeeded) { var pb = findBarByName(name); - writer.write(ANSI.hideCursor()); + rawWrite(ANSI.hideCursor()); if (pb != null && --pb.nesting == -1) { eraseBars(); @@ -590,7 +542,7 @@ else if (pb != null) { pb.update(); } - writer.write(ANSI.showCursor()); + rawWrite(ANSI.showCursor()); return -1; } @@ -617,7 +569,7 @@ public synchronized void warning(String message, ISourceLocation src) { eraseBars(); } - writer.println(("[WARNING] " + (src != null ? (src + ": ") : "") + message)); + rawPrintln(("[WARNING] " + (src != null ? (src + ": ") : "") + message)); if (!bars.isEmpty()) { printBars(); @@ -634,35 +586,34 @@ public synchronized void warning(String message, ISourceLocation src) { * is before the first character of a line. */ @Override - public synchronized void write(byte[] b) throws IOException { + public void write(String s, int off, int len) { if (!bars.isEmpty()) { eraseBars(); - findUnfinishedLine().write(b, out); + findUnfinishedLine().write(s, off, len); printBars(); } else { - out.write(b); + rawWrite(s, off, len); } } - /** * Here we make sure the progress bars are gone just before * someone wants to print in the console. When the printing * is ready, we simply add our own progress bars again. */ @Override - public synchronized void write(byte[] b, int off, int len) throws IOException { + public synchronized void write(char[] buf, int off, int len) { if (!bars.isEmpty()) { eraseBars(); - findUnfinishedLine().write(b, off, len, out); + findUnfinishedLine().write(buf, off, len); printBars(); } else { // this must be the raw output stream // otherwise rascal prompts (which do not end in newlines) will be buffered - out.write(b, off, len); + rawWrite(buf, off, len); } } @@ -672,14 +623,14 @@ public synchronized void write(byte[] b, int off, int len) throws IOException { * is ready, we simply add our own progress bars again. */ @Override - public synchronized void write(int b) throws IOException { + public synchronized void write(int c) { if (!bars.isEmpty()) { eraseBars(); - findUnfinishedLine().write(new byte[] { (byte) b }, out); + findUnfinishedLine().write(new char[] { (char) c }, 0, 1); printBars(); } else { - out.write(b); + rawWrite(c); } } @@ -694,17 +645,12 @@ public synchronized void endAllJobs() { } for (UnfinishedLine l : unfinishedLines) { - try { - l.flushLastLine(out); - } - catch (IOException e) { - // might happen if the terminal crashes before we stop running - } + l.flushLastLine(); } try { - writer.write(ANSI.showCursor()); - writer.flush(); + rawWrite(ANSI.showCursor()); + rawFlush(); out.flush(); } catch (IOException e) { @@ -713,7 +659,12 @@ public synchronized void endAllJobs() { } @Override - public void close() throws IOException { - endAllJobs(); + public void close() { + try { + endAllJobs(); + } + finally { + super.close(); + } } } diff --git a/src/org/rascalmpl/repl/completers/RascalCommandCompletion.java b/src/org/rascalmpl/repl/completers/RascalCommandCompletion.java new file mode 100644 index 00000000000..ac4e77ba27a --- /dev/null +++ b/src/org/rascalmpl/repl/completers/RascalCommandCompletion.java @@ -0,0 +1,137 @@ +package org.rascalmpl.repl.completers; + + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NavigableMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; +import org.rascalmpl.interpreter.utils.StringUtils; +import org.rascalmpl.interpreter.utils.StringUtils.OffsetLengthTerm; +import org.rascalmpl.repl.CompletionFunction; +import org.rascalmpl.repl.CompletionResult; + +public class RascalCommandCompletion implements Completer { + private static final NavigableMap COMMAND_KEYWORDS; + static { + COMMAND_KEYWORDS = new TreeMap<>(); + COMMAND_KEYWORDS.put("set", "change a evaluator setting"); + COMMAND_KEYWORDS.put("undeclare", "undeclare a local variable of the REPL"); + COMMAND_KEYWORDS.put("help", "print help message"); + COMMAND_KEYWORDS.put("edit", "open a rascal module in the editor"); + COMMAND_KEYWORDS.put("unimport", "unload an imported module from the REPL"); + COMMAND_KEYWORDS.put("declarations", "show declarations"); // TODO figure out what it does + COMMAND_KEYWORDS.put("quit", "cleanly exit the REPL"); + COMMAND_KEYWORDS.put("history", "history"); // TODO: figure out what it does + COMMAND_KEYWORDS.put("test", "run rest modules"); + COMMAND_KEYWORDS.put("modules", "show imported modules");// TODO: figure out what it does + COMMAND_KEYWORDS.put("clear", "clear evaluator");// TODO: figure out what it does + } + + private final NavigableMap setOptions; + private final BiConsumer> completeIdentifier; + private final BiConsumer> completeModule; + public RascalCommandCompletion(NavigableMap setOptions, BiConsumer> completeIdentifier, BiConsumer> completeModule) { + this.setOptions = setOptions; + this.completeIdentifier = completeIdentifier; + this.completeModule = completeModule; + } + + + + private static final Pattern splitCommand = Pattern.compile("^[\\t ]*:(?[a-z]*)([\\t ]|$)"); + /**@deprecated remove this function */ + public static CompletionResult complete(String line, int cursor, SortedSet commandOptions, CompletionFunction completeIdentifier, CompletionFunction completeModule) { + assert line.trim().startsWith(":"); + Matcher m = splitCommand.matcher(line); + if (m.find()) { + String currentCommand = m.group("command"); + switch(currentCommand) { + case "set": { + OffsetLengthTerm identifier = StringUtils.findRascalIdentifierAtOffset(line, cursor); + if (identifier != null && identifier.offset > m.end("command")) { + Collection suggestions = commandOptions.stream() + .filter(s -> s.startsWith(identifier.term)) + .sorted() + .collect(Collectors.toList()); + if (suggestions != null && ! suggestions.isEmpty()) { + return new CompletionResult(identifier.offset, suggestions); + } + } + else if (line.trim().equals(":set")) { + return new CompletionResult(line.length(), commandOptions); + } + return null; + } + case "undeclare": return completeIdentifier.complete(line, cursor); + case "edit": + case "unimport": return completeModule.complete(line, line.length()); + default: { + if (COMMAND_KEYWORDS.containsKey(currentCommand)) { + return null; // nothing to complete after a full command + } + List result = null; + if (currentCommand.isEmpty()) { + result = new ArrayList<>(COMMAND_KEYWORDS.keySet()); + } + else { + result = COMMAND_KEYWORDS.keySet().stream() + .filter(s -> s.startsWith(currentCommand)) + .collect(Collectors.toList()); + } + if (!result.isEmpty()) { + return new CompletionResult(m.start("command"), result); + } + } + } + } + return null; + } + + private static void generateCandidates(String partial, NavigableMap candidates, String group, List target) { + for (var can : candidates.subMap(partial, true, partial + Character.MAX_VALUE, false).entrySet()) { + target.add(new Candidate(can.getKey(), can.getKey(), group, can.getValue(), null, null, true)); + } + } + + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + var words = line.words(); + if (words.isEmpty() || !words.get(0).equals(":")) { + return; + } + if (line.wordIndex() == 1) { + // complete initial command/modifier + generateCandidates(line.word(), COMMAND_KEYWORDS, "interpreter modifiers", candidates); + return; + } + if (line.wordIndex() == 2) { + // complete arguments for first + switch (words.get(1)) { + case "set": + generateCandidates(line.word(), setOptions, "evaluator settings", candidates); + return; + case "undeclare": + completeIdentifier.accept(line.word(), candidates); + return; + case "edit": // intended fall-through + case "unimport": + completeModule.accept(line.word(), candidates); + return; + default: return; + } + } + // for the future it would be nice to also support completing thinks like `:set profiling ` + } + +} diff --git a/src/org/rascalmpl/repl/completers/RascalIdentifierCompletion.java b/src/org/rascalmpl/repl/completers/RascalIdentifierCompletion.java new file mode 100644 index 00000000000..1324dcdb6e0 --- /dev/null +++ b/src/org/rascalmpl/repl/completers/RascalIdentifierCompletion.java @@ -0,0 +1,54 @@ +package org.rascalmpl.repl.completers; + +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; + +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +public class RascalIdentifierCompletion implements Completer { + + private final BiFunction> lookupPartialIdentifiers; + + public RascalIdentifierCompletion(BiFunction> lookupPartialIdentifiers) { + this.lookupPartialIdentifiers = lookupPartialIdentifiers; + } + + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + boolean canBeIdentifier; + switch (line.words().get(0)) { + case ":": //fallthrough + // completion of settings for the REPL is handled elsewere + // it will also call this function in the 1 case where it's needed + case "import": // fallthrough + // not triggering on import of modules + case "extend": // fallthrough + // not triggering on extend of modules + canBeIdentifier = false; + break; + default: + canBeIdentifier = true; + break; + + } + if (canBeIdentifier) { + completePartialIdentifier(line.word(), candidates); + } + } + + public void completePartialIdentifier(String name, List candidates) { + name = RascalQualifiedNames.unescape(name); // remove escape that the interpreter cannot deal with + int qualifiedSplit = name.lastIndexOf("::"); + String qualifier = qualifiedSplit > -1 ? name.substring(0, qualifiedSplit) : ""; + String partial = qualifiedSplit > -1 ? name.substring(qualifiedSplit + 2) : name; + for (var can: lookupPartialIdentifiers.apply(qualifier, partial).entrySet()) { + String id = RascalQualifiedNames.escape(can.getKey()); + candidates.add(new Candidate(id, id, can.getValue(), null, null, null, false)); + } + } + +} diff --git a/src/org/rascalmpl/repl/completers/RascalKeywordCompletion.java b/src/org/rascalmpl/repl/completers/RascalKeywordCompletion.java new file mode 100644 index 00000000000..0005793a2ff --- /dev/null +++ b/src/org/rascalmpl/repl/completers/RascalKeywordCompletion.java @@ -0,0 +1,59 @@ +package org.rascalmpl.repl.completers; + +import java.util.List; +import java.util.NavigableMap; +import java.util.TreeMap; + +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +public class RascalKeywordCompletion implements Completer { + + private static final NavigableMap RASCAL_TYPE_KEYWORDS; + static { + RASCAL_TYPE_KEYWORDS = new TreeMap<>(); + RASCAL_TYPE_KEYWORDS.put("void", "a type without any values"); + RASCAL_TYPE_KEYWORDS.put("int", "sequence of digits of arbitrary length"); + RASCAL_TYPE_KEYWORDS.put("real", "real numbers with arbitrary size and precision"); + RASCAL_TYPE_KEYWORDS.put("num", "int/real/rat type"); + RASCAL_TYPE_KEYWORDS.put("bool", "boolean type"); + RASCAL_TYPE_KEYWORDS.put("data", "user-defined type (Algebraic Data Type)."); + RASCAL_TYPE_KEYWORDS.put("datetime", "date/time/datetime values"); + RASCAL_TYPE_KEYWORDS.put("list", "ordered sequence of values"); + RASCAL_TYPE_KEYWORDS.put("lrel", "lists of tuples with relational calculus"); + RASCAL_TYPE_KEYWORDS.put("loc", "source locations"); + RASCAL_TYPE_KEYWORDS.put("map", "a set of key/value pairs"); + RASCAL_TYPE_KEYWORDS.put("node", "untyped trees"); + RASCAL_TYPE_KEYWORDS.put("set", "unordered sequence of values"); + RASCAL_TYPE_KEYWORDS.put("rel", "sets of tuples with relational calculus"); + RASCAL_TYPE_KEYWORDS.put("str", "a sequence of unicode codepoints"); + RASCAL_TYPE_KEYWORDS.put("tuple", "a sequence of elements"); + RASCAL_TYPE_KEYWORDS.put("value", "all possible values"); + } + + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + var words = line.words(); + if (words.size() == 1) { + if ("import".startsWith(words.get(0))) { + add(candidates, "import", "statement", "import a module into the repl"); + } + if ("extend".startsWith(words.get(0))) { + add(candidates, "extend", "statement", "extend a module into the repl"); + } + } + var firstWord = words.get(0); + if (!firstWord.equals("import") && !firstWord.equals("extend") && !firstWord.equals(":")) { + for (var can: RASCAL_TYPE_KEYWORDS.subMap(line.word(), true, line.word() + Character.MAX_VALUE, false).entrySet()) { + add(candidates, can.getKey(), "type", can.getValue()); + } + } + } + + private static void add(List candidates, String value, String group, String description) { + candidates.add(new Candidate(value, value, group, description, null, null, true)); + } + +} diff --git a/src/org/rascalmpl/repl/completers/RascalLocationCompletion.java b/src/org/rascalmpl/repl/completers/RascalLocationCompletion.java new file mode 100644 index 00000000000..6f9965f8731 --- /dev/null +++ b/src/org/rascalmpl/repl/completers/RascalLocationCompletion.java @@ -0,0 +1,111 @@ +package org.rascalmpl.repl.completers; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Set; + +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; +import org.rascalmpl.uri.URIResolverRegistry; +import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.values.IRascalValueFactory; + +import io.usethesource.vallang.ISourceLocation; +import io.usethesource.vallang.IValueFactory; + +public class RascalLocationCompletion implements Completer { + + private static final IValueFactory VF = IRascalValueFactory.getInstance(); + private static final URIResolverRegistry REG = URIResolverRegistry.getInstance(); + + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + if (!line.word().startsWith("|")) { + return; + } + try { + String locCandidate = line.word().substring(1); + if (!locCandidate.contains("://")) { + // only complete scheme + completeSchema(candidates, locCandidate); + return; + } + if (completeAuthorities(candidates, locCandidate)) { + // we only had authorities to complete + return; + } + + // so we have at least a partial location + ISourceLocation directory = VF.sourceLocation(new URI(locCandidate)); + String fileName = ""; + if (!REG.isDirectory(directory)) { + // split filename and directory, to get to the actual directory + String fullPath = directory.getPath(); + int lastSeparator = fullPath.lastIndexOf('/'); + fileName = fullPath.substring(lastSeparator + 1); + fullPath = fullPath.substring(0, lastSeparator + 1); + directory = VF.sourceLocation(directory.getScheme(), directory.getAuthority(), fullPath); + if (!REG.isDirectory(directory)) { + return; + } + } + for (String currentFile : REG.listEntries(directory)) { + if (currentFile.startsWith(fileName)) { + add(candidates, URIUtil.getChildLocation(directory, currentFile)); + } + } + } + catch (URISyntaxException|IOException e) { + } + } + + private void add(List candidates, ISourceLocation loc) { + String locCandidate = loc.toString(); + if (REG.isDirectory(loc)) { + // remove trailing | so we can continue + // and add path separator + locCandidate = locCandidate.substring(0, locCandidate.length() - 1); + if (!locCandidate.endsWith("/")) { + locCandidate += "/"; + } + } + candidates.add(new Candidate(locCandidate, locCandidate, "location", null, null, null, false)); + } + + private boolean completeAuthorities(List candidates, String locCandidate) throws URISyntaxException, + IOException { + int lastSeparator = locCandidate.lastIndexOf('/'); + if (lastSeparator > 3 && locCandidate.substring(lastSeparator - 2, lastSeparator + 1).equals("://")) { + // special case, we want to complete authorities (but URI's without a authority are not valid) + String scheme = locCandidate.substring(0, lastSeparator - 2); + String partialAuthority = locCandidate.substring(lastSeparator + 1); + ISourceLocation root = VF.sourceLocation(scheme, "", ""); + for (String candidate: REG.listEntries(root)) { + if (candidate.startsWith(partialAuthority)) { + add(candidates, URIUtil.correctLocation(scheme, candidate, "")); + } + } + return true; + } + return false; + } + + private void completeSchema(List candidates, String locCandidate) { + filterCandidates(REG.getRegisteredInputSchemes(), candidates, locCandidate); + filterCandidates(REG.getRegisteredLogicalSchemes(), candidates, locCandidate); + filterCandidates(REG.getRegisteredOutputSchemes(), candidates, locCandidate); + } + + private void filterCandidates(Set src, List target, String prefix) { + for (String s : src) { + if (s.startsWith(prefix)) { + add(target, URIUtil.rootLocation(s)); + } + } + } + +} diff --git a/src/org/rascalmpl/repl/completers/RascalModuleCompletion.java b/src/org/rascalmpl/repl/completers/RascalModuleCompletion.java new file mode 100644 index 00000000000..b60b2e95a93 --- /dev/null +++ b/src/org/rascalmpl/repl/completers/RascalModuleCompletion.java @@ -0,0 +1,48 @@ +package org.rascalmpl.repl.completers; + +import java.util.List; +import java.util.function.Function; + +import org.jline.reader.Candidate; +import org.jline.reader.Completer; +import org.jline.reader.LineReader; +import org.jline.reader.ParsedLine; + +public class RascalModuleCompletion implements Completer { + + private final Function> searchPathLookup; + + public RascalModuleCompletion(Function> searchPathLookup) { + this.searchPathLookup = searchPathLookup; + } + + @Override + public void complete(LineReader reader, ParsedLine line, List candidates) { + if (line.wordIndex() != 1) { + // we can only complete import/extend statements that reference a module + return; + } + switch (line.words().get(0)) { + case "import": // intended fallthrough + case "extend": + completeModuleNames(line.word(), candidates, true); + return; + default: + return; + } + } + + public void completeModuleNames(String word, List candidates, boolean importStatement) { + // as jline will take care to filter prefixes, we only have to report modules in the directory (or siblings of the name) + // we do not have to filter out prefixes + word = RascalQualifiedNames.unescape(word); // remove escape that the interpreter cannot deal with + int rootedIndex = word.lastIndexOf("::"); + String moduleRoot = rootedIndex == -1? "": word.substring(0, rootedIndex); + String modulePrefix = moduleRoot.isEmpty() ? "" : moduleRoot + "::"; + for (var mod : searchPathLookup.apply(moduleRoot)) { + var fullPath = RascalQualifiedNames.escape(modulePrefix + mod); + var isFullModulePath = !mod.endsWith("::"); + candidates.add(new Candidate(fullPath + (isFullModulePath & importStatement? ";" : ""), fullPath, "modules", null, null, null, false)); + } + } +} diff --git a/src/org/rascalmpl/repl/completers/RascalQualifiedNames.java b/src/org/rascalmpl/repl/completers/RascalQualifiedNames.java new file mode 100644 index 00000000000..620f5382af6 --- /dev/null +++ b/src/org/rascalmpl/repl/completers/RascalQualifiedNames.java @@ -0,0 +1,118 @@ +package org.rascalmpl.repl.completers; + +import java.io.IOException; +import java.io.Reader; +import java.net.URISyntaxException; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.rascalmpl.uri.URIResolverRegistry; +import org.rascalmpl.values.ValueFactoryFactory; + +/** + * Make sure we generate escapes before rascal keywords, such that `lang::rascal::syntax` becomes `lang::rascal::\syntax` + */ +public class RascalQualifiedNames { + + private static final Pattern splitIdentifiers = Pattern.compile("[:][:]"); + + public static String escape(String name) { + return splitIdentifiers.splitAsStream(name + " ") // add space such that the last "::" is not lost + .map(RascalQualifiedNames::escapeKeyword) + .collect(Collectors.joining("::")).trim(); + } + public static String unescape(String term) { + return splitIdentifiers.splitAsStream(term + " ") // add space such that the last "::" is not lost + .map(RascalQualifiedNames::unescapeKeyword) + .collect(Collectors.joining("::")).trim() + ; + } + + private static final Set RASCAL_KEYWORDS = new HashSet(); + + private static void assureKeywordsAreScrapped() { + // TODO: replace this with the `util::Reflective::getRascalReservedIdentifiers` + // BUT! it doesn't contain all the keywords, it's missing the `BasicType` ones like `int` etc + if (RASCAL_KEYWORDS.isEmpty()) { + synchronized (RASCAL_KEYWORDS) { + if (!RASCAL_KEYWORDS.isEmpty()) { + return; + } + + String rascalGrammar = ""; + URIResolverRegistry reg = URIResolverRegistry.getInstance(); + try (Reader grammarReader = reg.getCharacterReader(ValueFactoryFactory.getValueFactory().sourceLocation("std", "", "/lang/rascal/syntax/Rascal.rsc"))) { + StringBuilder res = new StringBuilder(); + char[] chunk = new char[8 * 1024]; + int read; + while ((read = grammarReader.read(chunk, 0, chunk.length)) != -1) { + res.append(chunk, 0, read); + } + rascalGrammar = res.toString(); + } + catch (IOException | URISyntaxException e) { + e.printStackTrace(); + } + if (!rascalGrammar.isEmpty()) { + /* + * keyword RascalKeywords + * = "o" + * | "syntax" + * | "keyword" + * | "lexical" + * ... + * ; + */ + Pattern findKeywordSection = Pattern.compile("^\\s*keyword([^=]|\\s)*=(?([^;]|\\s)*);", Pattern.MULTILINE); + Matcher m = findKeywordSection.matcher(rascalGrammar); + if (m.find()) { + String keywords = "|" + m.group("keywords"); + Pattern keywordEntry = Pattern.compile("\\s*[|]\\s*[\"](?[^\"]*)[\"]"); + m = keywordEntry.matcher(keywords); + while (m.find()) { + RASCAL_KEYWORDS.add(m.group("keyword")); + } + } + /* + * syntax BasicType + = \value: "value" + | \loc: "loc" + | \node: "node" + */ + Pattern findBasicTypeSection = Pattern.compile("^\\s*syntax\\s*BasicType([^=]|\\s)*=(?([^;]|\\s)*);", Pattern.MULTILINE); + m = findBasicTypeSection.matcher(rascalGrammar); + if (m.find()) { + String keywords = "|" + m.group("keywords"); + Pattern keywordEntry = Pattern.compile("\\s*[|][^:]*:\\s*[\"](?[^\"]*)[\"]"); + m = keywordEntry.matcher(keywords); + while (m.find()) { + RASCAL_KEYWORDS.add(m.group("keyword")); + } + } + } + if (RASCAL_KEYWORDS.isEmpty()) { + RASCAL_KEYWORDS.add("syntax"); + } + } + } + } + + private static String escapeKeyword(String s) { + assureKeywordsAreScrapped(); + if (RASCAL_KEYWORDS.contains(s.trim())) { + return "\\" + s; + } + return s; + } + + private static String unescapeKeyword(String s) { + if (s.startsWith("\\") && !s.contains("-")) { + return s.substring(1); + } + return s; + } + +} diff --git a/src/org/rascalmpl/repl/jline3/RascalLineParser.java b/src/org/rascalmpl/repl/jline3/RascalLineParser.java new file mode 100644 index 00000000000..f754f157b43 --- /dev/null +++ b/src/org/rascalmpl/repl/jline3/RascalLineParser.java @@ -0,0 +1,280 @@ +package org.rascalmpl.repl.jline3; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; +import org.jline.reader.EOFError; +import org.jline.reader.ParsedLine; +import org.jline.reader.Parser; +import org.jline.reader.SyntaxError; +import org.rascalmpl.parser.gtd.exception.ParseError; +import org.rascalmpl.values.parsetrees.ITree; +import org.rascalmpl.values.parsetrees.TreeAdapter; + + + +public class RascalLineParser implements Parser { + + private final Function commandParser; + + public RascalLineParser(Function commandParser) { + this.commandParser = commandParser; + } + + @Override + public ParsedLine parse(String line, int cursor, ParseContext context) throws SyntaxError { + switch (context) { + case UNSPECIFIED: // TODO: check if this is correct + case ACCEPT_LINE: + // we have to verify the input is correct rascal statement + return parseFullRascalCommand(line, cursor); + case COMPLETE: + // for completion purposes, we want a specific kind of grouping + // so we'll use a heuristic for this. + // in the future we might be able to use the parser with error recovery + // but we would still have to think about grouping things together that aren't in the + // parse tree, such as `:` and the `set` + try { + // lets see, maybe it parses as a rascal expression + return parseFullRascalCommand(line, cursor); + } + catch (EOFError e) { + // otherwise fallback to regex party + return splitWordsOnly(line, cursor); + } + case SECONDARY_PROMPT: + throw new SyntaxError(-1, -1, "Unsupported SECONDARY_PROMPT"); + case SPLIT_LINE: + throw new SyntaxError(-1, -1, "Unsupported SPLIT_LINE"); + default: + throw new UnsupportedOperationException("Unimplemented context: " + context); + } + } + + private ParsedLine splitWordsOnly(String line, int cursor) { + // small line parser, in the future we might be able to use error recovery + var words = new ArrayList(); + parseWords(line, 0, words); + return new ParsedLineLexedWords(words, cursor, line); + } + + private void parseWords(String buffer, int position, List words) { + /** are we interpolating inside of a string */ + boolean inString = false; + boolean inLocation = false; + while (position < buffer.length()) { + position = eatWhiteSpace(buffer, position); + if (position >= buffer.length()) { + return; + } + char c = buffer.charAt(position); + boolean isWord = true; + int wordEnd = position; + if (c == '"' || (c == '>' && inString)) { + wordEnd = parseEndedAfter(buffer, position, RASCAL_STRING); + inString = wordEnd != buffer.length() && buffer.charAt(wordEnd - 1) != '"'; + } + else if (c == '|' || (c == '>' && inLocation)) { + wordEnd = parseEndedAfter(buffer, position, RASCAL_LOCATION); + inLocation = wordEnd != buffer.length() && buffer.charAt(wordEnd - 1) == '<'; + } + else if (Character.isJavaIdentifierPart(c) || c == '\\') { + wordEnd = parseEndedAfter(buffer, position, RASCAL_NAME); + } + else if (c == ':' && words.isEmpty()) { + // can be a command start + wordEnd++; + } + else { + wordEnd++; + isWord = false; + } + + if (wordEnd == position) { + wordEnd = buffer.length(); + } + + if (isWord) { + words.add(new LexedWord(buffer, position, wordEnd)); + } + position = wordEnd; + } + } + + private final class ParsedLineLexedWords implements ParsedLine { + private final ArrayList words; + private final int cursor; + private final String line; + private final @MonotonicNonNull LexedWord atCursor; + + private ParsedLineLexedWords(ArrayList words, int cursor, String line) { + this.words = words; + this.cursor = cursor; + this.line = line; + if (cursor >= (line.length() - 1)) { + if (words.isEmpty() || !words.get(words.size() - 1).cursorInside(cursor)) { + words.add(new LexedWord(line + " ", cursor, cursor)); + } + } + + atCursor = words.stream() + .filter(l -> l.cursorInside(cursor)) + .findFirst() + .orElse(null); + } + + @Override + public String word() { + return atCursor == null ? "" : atCursor.word(); + } + + @Override + public int wordCursor() { + return atCursor == null ? 0 : (cursor - atCursor.begin); + } + + @Override + public int wordIndex() { + return atCursor == null ? -1 : words.indexOf(atCursor); + } + + @Override + public List words() { + return words.stream() + .map(LexedWord::word) + .collect(Collectors.toList()); + } + + @Override + public String line() { + return line; + } + + @Override + public int cursor() { + return cursor; + } + } + + + private static class LexedWord { + + private final String buffer; + private final int begin; + private final int end; + + public LexedWord(String buffer, int begin, int end) { + this.buffer = buffer; + this.begin = begin; + this.end = end; + } + + public boolean cursorInside(int cursor) { + // TODO: review around edges + return begin <= cursor && cursor <= end; + } + + String word() { + return buffer.substring(begin, end); + } + } + + private static int parseEndedAfter(String buffer, int position, Pattern parser) { + var matcher = parser.matcher(buffer); + matcher.region(position, buffer.length()); + if (!matcher.find()) { + return position; + } + return matcher.end(); + } + + // strings with rudementary interpolation support + private static final Pattern RASCAL_STRING + = Pattern.compile("^[\">]([^\"<\\\\]|([\\\\].))*[\"<]"); + // locations with rudementary interpolation support + private static final Pattern RASCAL_LOCATION + = Pattern.compile("^[\\|\\>][^\\|\\<\\t-\\n\\r ]*[\\|\\<]?"); + + private static final Pattern RASCAL_NAME + = Pattern.compile("^((([A-Za-z_][A-Za-z0-9_]*)|([\\\\][A-Za-z_]([\\-A-Za-z0-9_])*))(::)?)+"); + + // only unicode spaces & multi-line comments + private static final Pattern RASCAL_WHITE_SPACE + = Pattern.compile("^(\\p{Zs}|([/][*]([^*]|([*][^/]))*[*][/]))*"); + + private int eatWhiteSpace(String buffer, int position) { + return parseEndedAfter(buffer, position, RASCAL_WHITE_SPACE); + } + + private ParsedLine parseFullRascalCommand(String line, int cursor) throws SyntaxError { + // TODO: to support inline highlighting, we have to remove the ansi escapes before parsing + try { + return translateTree(commandParser.apply(line), line, cursor); + } + catch (ParseError pe) { + throw new EOFError(pe.getBeginLine(), pe.getBeginColumn(), "Parse error"); + } + catch (Throwable e) { + throw new EOFError(-1, -1, "Unexpected failure during pasing of the command: " + e.getMessage()); + } + } + + + private ParsedLine translateTree(ITree command, String line, int cursor) { + // todo: return CompletingParsedLine so that we can also help with quoting completion + var result = new ArrayList(); + + collectWords(command, result, line, 0); + return new ParsedLineLexedWords(result, cursor, line); + } + + private int collectWords(ITree t, List words, String line, int offset) { + boolean isWord; + if (TreeAdapter.isLayout(t)) { + isWord = false; + } + else if (TreeAdapter.isLexical(t) || TreeAdapter.isLiteral(t) || TreeAdapter.isCILiteral(t)) { + isWord = true; + } + else if (TreeAdapter.isSort(t) && TreeAdapter.getSortName(t).equals("QualifiedName")) { + isWord = true; + } + else if (TreeAdapter.isSort(t)) { + var loc = TreeAdapter.getLocation(t); + isWord = false; + for (var c : t.getArgs()) { + if (c instanceof ITree) { + offset = collectWords((ITree)c, words, line, offset); + } + } + return loc == null ? offset : (loc.getOffset() + loc.getLength()); + } + else if (TreeAdapter.isTop(t)) { + isWord = false; + var args = t.getArgs(); + var preLoc = TreeAdapter.getLocation((ITree)args.get(0)); + offset += preLoc == null ? 0 : preLoc.getLength(); + offset = collectWords((ITree)args.get(1), words, line, offset); + var postLoc = TreeAdapter.getLocation((ITree)args.get(2)); + return offset + (postLoc == null ? 0 : postLoc.getLength()); + } + else { + isWord = false; + } + + var loc = TreeAdapter.getLocation(t); + var length = loc == null ? TreeAdapter.yield(t).length() : loc.getLength(); + if (isWord) { + words.add(new LexedWord(line, offset, offset + length)); + return offset + length; + } + else { + return offset + length; + } + + } +} diff --git a/src/org/rascalmpl/semantics/dynamic/Import.java b/src/org/rascalmpl/semantics/dynamic/Import.java index ebb4656494a..6ecd620f373 100644 --- a/src/org/rascalmpl/semantics/dynamic/Import.java +++ b/src/org/rascalmpl/semantics/dynamic/Import.java @@ -250,7 +250,7 @@ else if (eval.getCurrentEnvt() == eval.__getRootScope()) { addImportToCurrentModule(src, name, eval); if (heap.getModule(name).isDeprecated()) { - eval.getErrorPrinter().println(src + ":" + name + " is deprecated, " + heap.getModule(name).getDeprecatedMessage()); + eval.getStdErr().println(src + ":" + name + " is deprecated, " + heap.getModule(name).getDeprecatedMessage()); } return; @@ -312,7 +312,7 @@ public static ModuleEnvironment loadModule(ISourceLocation x, String name, IEval Module module = buildModule(uri, env, eval, jobName); if (isDeprecated(module)) { - eval.getErrorPrinter().println("WARNING: deprecated module " + name + ":" + getDeprecatedMessage(module)); + eval.getStdErr().println("WARNING: deprecated module " + name + ":" + getDeprecatedMessage(module)); } if (module != null) { diff --git a/src/org/rascalmpl/semantics/dynamic/ShellCommand.java b/src/org/rascalmpl/semantics/dynamic/ShellCommand.java index 8aecec7932a..b57e5efd2f7 100644 --- a/src/org/rascalmpl/semantics/dynamic/ShellCommand.java +++ b/src/org/rascalmpl/semantics/dynamic/ShellCommand.java @@ -44,14 +44,14 @@ public Result interpret(IEvaluator> __eval) { ISourceLocation uri = __eval.getRascalResolver().resolveModule(name); if (uri == null) { - __eval.getErrorPrinter().println("module " + name + " can not be found in the search path."); + __eval.getStdErr().println("module " + name + " can not be found in the search path."); } else { services.edit(uri); } } else { - __eval.getErrorPrinter().println("The current Rascal execution environment does not know how to start an editor."); + __eval.getStdErr().println("The current Rascal execution environment does not know how to start an editor."); } return org.rascalmpl.interpreter.result.ResultFactory.nothing(); @@ -81,7 +81,7 @@ public Help(ISourceLocation __param1, IConstructor tree) { public Result interpret(IEvaluator> __eval) { __eval.setCurrentAST(this); - __eval.printHelpMessage(__eval.getOutPrinter()); + __eval.printHelpMessage(__eval.getStdOut()); return org.rascalmpl.interpreter.result.ResultFactory.nothing(); } diff --git a/src/org/rascalmpl/shell/EclipseTerminalConnection.java b/src/org/rascalmpl/shell/EclipseTerminalConnection.java index 66d017901b5..8656f9c8ec6 100644 --- a/src/org/rascalmpl/shell/EclipseTerminalConnection.java +++ b/src/org/rascalmpl/shell/EclipseTerminalConnection.java @@ -17,11 +17,23 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.net.InetAddress; import java.net.Socket; +import java.nio.charset.Charset; import java.util.Arrays; +import java.util.function.IntConsumer; +import java.util.function.IntSupplier; + +import org.jline.terminal.Attributes; +import org.jline.terminal.Cursor; +import org.jline.terminal.MouseEvent; +import org.jline.terminal.Size; +import org.jline.terminal.Terminal; +import org.jline.utils.ColorPalette; +import org.jline.utils.InfoCmp.Capability; +import org.jline.utils.NonBlockingReader; -import jline.Terminal; public class EclipseTerminalConnection implements Terminal { public static final byte[] HEADER = new byte[] { 0x42, 0x42 }; @@ -78,70 +90,147 @@ private int askForHeight() { return previousHeight; } } + @Override - public void init() throws Exception { - base.init(); + public Size getSize() { + return new Size(askForWidth(), askForHeight()); } - @Override - public void restore() throws Exception { - base.restore(); + public void setSize(Size size) { + base.setSize(size); } @Override - public void reset() throws Exception { - base.reset(); + public void close() throws IOException { + base.close(); } - @Override - public boolean isSupported() { - return base.isSupported(); + public String getName() { + return base.getName(); } - - @Override - public boolean isAnsiSupported() { - return base.isAnsiSupported(); + public SignalHandler handle(Signal signal, SignalHandler handler) { + return base.handle(signal, handler); } - @Override - public OutputStream wrapOutIfNeeded(OutputStream out) { - return base.wrapOutIfNeeded(out); + public void raise(Signal signal) { + base.raise(signal); } - @Override - public InputStream wrapInIfNeeded(InputStream in) throws IOException { - return base.wrapInIfNeeded(in); + public NonBlockingReader reader() { + return base.reader(); } - @Override - public boolean hasWeirdWrap() { - return base.hasWeirdWrap(); + public PrintWriter writer() { + return base.writer(); } - @Override - public boolean isEchoEnabled() { - return base.isEchoEnabled(); + public Charset encoding() { + return base.encoding(); } - @Override - public void setEchoEnabled(boolean enabled) { - base.setEchoEnabled(enabled); + public InputStream input() { + return base.input(); + } + @Override + public OutputStream output() { + return base.output(); + } + @Override + public boolean canPauseResume() { + return base.canPauseResume(); + } + @Override + public void pause() { + base.pause(); + } + @Override + public void pause(boolean wait) throws InterruptedException { + base.pause(wait); + } + @Override + public void resume() { + base.resume(); + } + @Override + public boolean paused() { + return base.paused(); + } + @Override + public Attributes enterRawMode() { + return base.enterRawMode(); + } + @Override + public boolean echo() { + return base.echo(); + } + @Override + public boolean echo(boolean echo) { + return base.echo(echo); + } + @Override + public Attributes getAttributes() { + return base.getAttributes(); + } + @Override + public void setAttributes(Attributes attr) { + base.setAttributes(attr); + } + @Override + public void flush() { + base.flush(); + } + @Override + public String getType() { + return base.getType(); + } + @Override + public boolean puts(Capability capability, Object... params) { + return base.puts(capability, params); + } + @Override + public boolean getBooleanCapability(Capability capability) { + return base.getBooleanCapability(capability); + } + @Override + public Integer getNumericCapability(Capability capability) { + return base.getNumericCapability(capability); + } + @Override + public String getStringCapability(Capability capability) { + return base.getStringCapability(capability); + } + @Override + public Cursor getCursorPosition(IntConsumer discarded) { + return base.getCursorPosition(discarded); + } + @Override + public boolean hasMouseSupport() { + return base.hasMouseSupport(); + } + @Override + public boolean trackMouse(MouseTracking tracking) { + return base.trackMouse(tracking); + } + @Override + public MouseEvent readMouseEvent() { + return base.readMouseEvent(); + } + @Override + public MouseEvent readMouseEvent(IntSupplier reader) { + return base.readMouseEvent(reader); } - @Override - public String getOutputEncoding() { - return base.getOutputEncoding(); + public boolean hasFocusSupport() { + return base.hasFocusSupport(); } - @Override - public void enableInterruptCharacter() { - base.enableInterruptCharacter(); + public boolean trackFocus(boolean tracking) { + return base.trackFocus(tracking); } - @Override - public void disableInterruptCharacter() { - base.disableInterruptCharacter(); + public ColorPalette getPalette() { + return base.getPalette(); } } diff --git a/src/org/rascalmpl/shell/ModuleRunner.java b/src/org/rascalmpl/shell/ModuleRunner.java index 7d78fd3c00b..03f929ca05b 100644 --- a/src/org/rascalmpl/shell/ModuleRunner.java +++ b/src/org/rascalmpl/shell/ModuleRunner.java @@ -1,8 +1,8 @@ package org.rascalmpl.shell; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.interpreter.Evaluator; @@ -15,7 +15,7 @@ public class ModuleRunner implements ShellRunner { private final Evaluator eval; - public ModuleRunner(InputStream input, OutputStream stdout, OutputStream stderr, IRascalMonitor monitor) { + public ModuleRunner(Reader input, PrintWriter stdout, PrintWriter stderr, IRascalMonitor monitor) { eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr, monitor); } @@ -34,8 +34,8 @@ public void run(String args[]) throws IOException { IValue v = eval.main(eval.getMonitor(), module, "main", realArgs); if (v != null && !(v instanceof IInteger)) { - new StandardTextWriter(true).write(v, eval.getOutPrinter()); - eval.getOutPrinter().flush(); + new StandardTextWriter(true).write(v, eval.getStdOut()); + eval.getStdOut().flush(); } System.exit(v instanceof IInteger ? ((IInteger) v).intValue() : 0); diff --git a/src/org/rascalmpl/shell/ParseTest.java b/src/org/rascalmpl/shell/ParseTest.java new file mode 100644 index 00000000000..ba93355ccc3 --- /dev/null +++ b/src/org/rascalmpl/shell/ParseTest.java @@ -0,0 +1,91 @@ +package org.rascalmpl.shell; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; +import java.util.Queue; + +import org.rascalmpl.library.Prelude; +import org.rascalmpl.library.lang.rascal.syntax.RascalParser; +import org.rascalmpl.parser.Parser; +import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener; +import org.rascalmpl.parser.uptr.UPTRNodeFactory; +import org.rascalmpl.parser.uptr.action.NoActionExecutor; +import org.rascalmpl.uri.URIResolverRegistry; +import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.values.parsetrees.ITree; +import org.rascalmpl.values.parsetrees.TreeAdapter; + +import io.usethesource.vallang.IConstructor; +import io.usethesource.vallang.ISourceLocation; + +public class ParseTest { + + private static ITree parseFile(ISourceLocation f, char[] data) { + return new RascalParser().parse(Parser.START_MODULE, f.getURI(), data, new NoActionExecutor(), + new DefaultNodeFlattener(), new UPTRNodeFactory(true)); + } + + private static void progress(String s, Object... args) { + System.err.println(String.format(s, args)); + } + + private static List> findFiles() throws URISyntaxException, IOException { + final var reg = URIResolverRegistry.getInstance(); + Queue worklist = new LinkedList<>(); + worklist.add(URIUtil.correctLocation("cwd", null, "/src")); + // URIUtil.createFileLocation("d:\\swat.engineering\\rascal\\rascal\\src")); + List> result = new ArrayList<>(); + ISourceLocation next; + while ((next = worklist.poll()) != null) { + var children = reg.list(next); + for (var c: children) { + if (reg.isDirectory(c)) { + worklist.add(c); + } + else if (c.getPath().endsWith(".rsc")) { + try (var r = reg.getCharacterReader(c)) { + result.add(new AbstractMap.SimpleEntry<>(c, Prelude.consumeInputStream(r).toCharArray())); + } + } + } + } + return result; + + + } + + static final int parsingRounds = 10; + static final int warmupRounds = 2; + + public static void main(String[] args) throws URISyntaxException, IOException { + progress("Collecting rascal files"); + var targets = findFiles(); + progress("Found: %d files, press enter to start parsing", targets.size()); + System.in.read(); + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + progress("Start parsing"); + + long count = 0; + long totalTime = 0; + + for (int i = 0; i < warmupRounds + parsingRounds; i++) { + var start = System.nanoTime(); + for (var t : targets) { + var tr = parseFile(t.getKey(), t.getValue()); + count += TreeAdapter.getLocation(TreeAdapter.getStartTop(tr)).getLength(); + } + var stop = System.nanoTime(); + progress("%s took: %d ms", i < warmupRounds ? "Warmup" : "Parsing", TimeUnit.NANOSECONDS.toMillis(stop - start)); + if (i >= warmupRounds) { + totalTime += stop - start; + } + } + progress("Done: %d, average time: %d ms", count, TimeUnit.NANOSECONDS.toMillis(totalTime / parsingRounds)); + } +} diff --git a/src/org/rascalmpl/shell/REPLRunner.java b/src/org/rascalmpl/shell/REPLRunner.java_disabled old mode 100755 new mode 100644 similarity index 100% rename from src/org/rascalmpl/shell/REPLRunner.java rename to src/org/rascalmpl/shell/REPLRunner.java_disabled diff --git a/src/org/rascalmpl/shell/RascalShell.java b/src/org/rascalmpl/shell/RascalShell.java_disabled similarity index 100% rename from src/org/rascalmpl/shell/RascalShell.java rename to src/org/rascalmpl/shell/RascalShell.java_disabled diff --git a/src/org/rascalmpl/shell/RascalShell2.java b/src/org/rascalmpl/shell/RascalShell2.java new file mode 100644 index 00000000000..a273ac431c3 --- /dev/null +++ b/src/org/rascalmpl/shell/RascalShell2.java @@ -0,0 +1,166 @@ +/* +Copyright (c) 2024, Swat.engineering +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package org.rascalmpl.shell; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; + +import org.jline.terminal.TerminalBuilder; +import org.jline.utils.OSUtils; +import org.rascalmpl.ideservices.BasicIDEServices; +import org.rascalmpl.ideservices.IDEServices; +import org.rascalmpl.interpreter.Evaluator; +import org.rascalmpl.interpreter.env.GlobalEnvironment; +import org.rascalmpl.interpreter.env.ModuleEnvironment; +import org.rascalmpl.interpreter.load.StandardLibraryContributor; +import org.rascalmpl.interpreter.utils.RascalManifest; +import org.rascalmpl.repl.BaseREPL2; +import org.rascalmpl.repl.RascalReplServices; +import org.rascalmpl.repl.TerminalProgressBarMonitor; +import org.rascalmpl.values.ValueFactoryFactory; + +import io.usethesource.vallang.IValueFactory; + + +public class RascalShell2 { + + private static void printVersionNumber(){ + System.err.println("Version: " + RascalManifest.getRascalVersionNumber()); + } + + public static void main(String[] args) throws IOException { + System.setProperty("apple.awt.UIElement", "true"); // turns off the annoying desktop icon + printVersionNumber(); + + //try { + var termBuilder = TerminalBuilder.builder(); + if (OSUtils.IS_WINDOWS) { + termBuilder.encoding(StandardCharsets.UTF_8); + } + termBuilder.dumb(true); // fallback to dumb terminal if detected terminal is not supported + var term = termBuilder.build(); + + + var repl = new BaseREPL2(new RascalReplServices((t) -> { + var monitor = new TerminalProgressBarMonitor(term); + IDEServices services = new BasicIDEServices(term.writer(), monitor); + + + GlobalEnvironment heap = new GlobalEnvironment(); + ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); + IValueFactory vf = ValueFactoryFactory.getValueFactory(); + Evaluator evaluator = new Evaluator(vf, term.reader(), new PrintWriter(System.err, true), term.writer(), root, heap, monitor); + evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); + return evaluator; + }), term); + repl.run(); + /* + + + //IRascalMonitor monitor = IRascalMonitor.buildConsoleMonitor(System.in, System.out, true); + var monitor = new TerminalProgressBarMonitor(term); + + // var monitor = new NullRascalMonitor() { + // @Override + // public void warning(String message, ISourceLocation src) { + // reader.printAbove("[WARN] " + message); + // } + // }; + + IDEServices services = new BasicIDEServices(term.writer(), monitor); + + + GlobalEnvironment heap = new GlobalEnvironment(); + ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); + IValueFactory vf = ValueFactoryFactory.getValueFactory(); + Evaluator evaluator = new Evaluator(vf, term.reader(), new PrintWriter(System.err, true), term.writer(), root, heap, monitor); + evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); + + URIResolverRegistry reg = URIResolverRegistry.getInstance(); + + var indentedPrettyPrinter = new ReplTextWriter(true); + + while (true) { + String line = reader.readLine(Ansi.ansi().reset().bold().toString() + "rascal> " + Ansi.ansi().boldOff().toString()); + try { + + Result result; + + synchronized(evaluator) { + result = evaluator.eval(monitor, line, URIUtil.rootLocation("prompt")); + evaluator.endAllJobs(); + } + + if (result.isVoid()) { + monitor.println("ok"); + } + else { + IValue value = result.getValue(); + Type type = result.getStaticType(); + + if (type.isAbstractData() && type.isStrictSubtypeOf(RascalValueFactory.Tree) && !type.isBottom()) { + monitor.write("(" + type.toString() +") `"); + TreeAdapter.yield((IConstructor)value, true, monitor); + monitor.write("`"); + } + else { + indentedPrettyPrinter.write(value, monitor); + } + monitor.println(); + } + } + catch (InterruptException ie) { + reader.printAbove("Interrupted"); + try { + ie.getRascalStackTrace().prettyPrintedString(evaluator.getStdErr(), indentedPrettyPrinter); + } + catch (IOException e) { + } + } + catch (ParseError pe) { + parseErrorMessage(evaluator.getStdErr(), line, "prompt", pe, indentedPrettyPrinter); + } + catch (StaticError e) { + staticErrorMessage(evaluator.getStdErr(),e, indentedPrettyPrinter); + } + catch (Throw e) { + throwMessage(evaluator.getStdErr(),e, indentedPrettyPrinter); + } + catch (QuitException q) { + reader.printAbove("Quiting REPL"); + break; + } + catch (Throwable e) { + throwableMessage(evaluator.getStdErr(), e, evaluator.getStackTrace(), indentedPrettyPrinter); + } + } + System.exit(0); + } + catch (Throwable e) { + System.err.println("\n\nunexpected error: " + e.getMessage()); + e.printStackTrace(System.err); + System.exit(1); + } + finally { + System.out.flush(); + System.err.flush(); + } + */ + } + + + + +} diff --git a/src/org/rascalmpl/shell/ShellEvaluatorFactory.java b/src/org/rascalmpl/shell/ShellEvaluatorFactory.java index 63eafa3da38..bfb0b335080 100644 --- a/src/org/rascalmpl/shell/ShellEvaluatorFactory.java +++ b/src/org/rascalmpl/shell/ShellEvaluatorFactory.java @@ -4,6 +4,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Reader; import java.net.URISyntaxException; import org.rascalmpl.debug.IRascalMonitor; @@ -27,7 +29,7 @@ public class ShellEvaluatorFactory { - public static Evaluator getDefaultEvaluator(InputStream input, OutputStream stdout, OutputStream stderr, IRascalMonitor monitor) { + public static Evaluator getDefaultEvaluator(Reader input, PrintWriter stdout, PrintWriter stderr, IRascalMonitor monitor) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); @@ -46,7 +48,7 @@ public static Evaluator getDefaultEvaluator(InputStream input, OutputStream stdo return evaluator; } - public static Evaluator getDefaultEvaluatorForLocation(File fileOrFolderInProject, InputStream input, OutputStream stdout, OutputStream stderr, IRascalMonitor monitor) { + public static Evaluator getDefaultEvaluatorForLocation(File fileOrFolderInProject, Reader input, PrintWriter stdout, PrintWriter stderr, IRascalMonitor monitor) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); diff --git a/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java b/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java index bc27c37e117..5fc187e855e 100644 --- a/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java +++ b/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java @@ -13,6 +13,7 @@ import java.io.IOException; import java.io.PrintWriter; +import java.io.Reader; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; @@ -257,7 +258,7 @@ private void processModules() { synchronized(stdout) { evaluator.warning("Could not import " + module + " for testing...", null); evaluator.warning(e.getMessage(), null); - e.printStackTrace(evaluator.getOutPrinter()); + e.printStackTrace(evaluator.getStdOut()); } // register a failing module to make sure we report failure later on. @@ -300,7 +301,7 @@ private void initializeEvaluator() { heap = new GlobalEnvironment(); root = heap.addModule(new ModuleEnvironment("___junit_test___", heap)); - evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap, RascalJUnitTestRunner.getCommonMonitor()); + evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), Reader.nullReader(), new PrintWriter(System.err, true), new PrintWriter(System.out, false), root, heap, RascalJUnitTestRunner.getCommonMonitor()); stdout = new PrintWriter(evaluator.getStdOut()); stderr = new PrintWriter(evaluator.getStdErr()); diff --git a/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java b/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java index e2330988525..55a4a983fce 100644 --- a/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java +++ b/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java @@ -12,6 +12,8 @@ import java.io.File; import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; import java.lang.annotation.Annotation; import java.net.URISyntaxException; import java.util.ArrayList; @@ -20,6 +22,10 @@ import java.util.List; import java.util.Queue; +import org.jline.terminal.Terminal; +import org.jline.terminal.TerminalBuilder; +import org.jline.terminal.impl.DumbTerminal; +import org.jline.terminal.impl.DumbTerminalProvider; import org.junit.runner.Description; import org.junit.runner.Result; import org.junit.runner.Runner; @@ -36,7 +42,6 @@ import org.rascalmpl.interpreter.utils.RascalManifest; import org.rascalmpl.library.util.PathConfig; import org.rascalmpl.library.util.PathConfig.RascalConfigMode; -import org.rascalmpl.shell.RascalShell; import org.rascalmpl.shell.ShellEvaluatorFactory; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; @@ -50,7 +55,22 @@ public class RascalJUnitTestRunner extends Runner { private static class InstanceHolder { - final static IRascalMonitor monitor = IRascalMonitor.buildConsoleMonitor(System.in, System.out); + final static IRascalMonitor monitor; + static { + Terminal tm; + try { + tm = TerminalBuilder.terminal(); + } + catch (IOException e) { + try { + tm = new DumbTerminal(System.in, System.err); + } + catch (IOException e1) { + throw new IllegalStateException("Could not create a terminal representation"); + } + } + monitor = IRascalMonitor.buildConsoleMonitor(tm); + } } public static IRascalMonitor getCommonMonitor() { @@ -68,12 +88,9 @@ public static IRascalMonitor getCommonMonitor() { static { try { - RascalShell.setupWindowsCodepage(); - RascalShell.enableWindowsAnsiEscapesIfPossible(); - heap = new GlobalEnvironment(); root = heap.addModule(new ModuleEnvironment("___junit_test___", heap)); - evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap, getCommonMonitor()); + evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), Reader.nullReader(), new PrintWriter(System.err, true), new PrintWriter(System.out, false), root, heap, getCommonMonitor()); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); evaluator.getConfiguration().setErrors(true); diff --git a/src/org/rascalmpl/test/infrastructure/TestFramework.java b/src/org/rascalmpl/test/infrastructure/TestFramework.java index ace8f0f08d5..12ae1d0a210 100644 --- a/src/org/rascalmpl/test/infrastructure/TestFramework.java +++ b/src/org/rascalmpl/test/infrastructure/TestFramework.java @@ -22,12 +22,14 @@ import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.Reader; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Set; import org.junit.After; +import org.rascalmpl.ast.Char; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; @@ -56,10 +58,10 @@ public class TestFramework { heap = new GlobalEnvironment(); root = heap.addModule(new ModuleEnvironment("___test___", heap)); - evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, RascalJUnitTestRunner.getCommonMonitor(), root, heap); + evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), Reader.nullReader(), new PrintWriter(System.err, true), new PrintWriter(System.out, false), RascalJUnitTestRunner.getCommonMonitor(), root, heap); - stdout = evaluator.getOutPrinter(); - stderr = evaluator.getErrorPrinter(); + stdout = evaluator.getStdOut(); + stderr = evaluator.getStdErr(); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); diff --git a/src/org/rascalmpl/values/parsetrees/TreeAdapter.java b/src/org/rascalmpl/values/parsetrees/TreeAdapter.java index 4ab329a2a92..3b631fb1245 100644 --- a/src/org/rascalmpl/values/parsetrees/TreeAdapter.java +++ b/src/org/rascalmpl/values/parsetrees/TreeAdapter.java @@ -19,9 +19,10 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import org.fusesource.jansi.Ansi; -import org.fusesource.jansi.Ansi.Attribute; -import org.fusesource.jansi.Ansi.Color; + +import org.jline.jansi.Ansi; +import org.jline.jansi.Ansi.Attribute; +import org.jline.jansi.Ansi.Color; import org.rascalmpl.exceptions.ImplementationError; import org.rascalmpl.interpreter.utils.LimitedResultWriter; import org.rascalmpl.values.RascalValueFactory; diff --git a/test/org/rascalmpl/MatchFingerprintTest.java b/test/org/rascalmpl/MatchFingerprintTest.java index 4b2126b4010..e210f36a191 100644 --- a/test/org/rascalmpl/MatchFingerprintTest.java +++ b/test/org/rascalmpl/MatchFingerprintTest.java @@ -15,6 +15,8 @@ import static org.junit.Assert.assertNotEquals; import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; import java.io.StringReader; import org.rascalmpl.interpreter.Evaluator; @@ -47,7 +49,7 @@ public class MatchFingerprintTest extends TestCase { private final GlobalEnvironment heap = new GlobalEnvironment(); private final ModuleEnvironment root = new ModuleEnvironment("root", heap); - private final Evaluator eval = new Evaluator(IRascalValueFactory.getInstance(), System.in, System.err, System.out, root, heap, RascalJUnitTestRunner.getCommonMonitor()); + private final Evaluator eval = new Evaluator(IRascalValueFactory.getInstance(), Reader.nullReader(), new PrintWriter(System.err, true), new PrintWriter(System.out), root, heap, RascalJUnitTestRunner.getCommonMonitor()); private final RascalFunctionValueFactory VF = eval.getFunctionValueFactory(); private final TypeFactory TF = TypeFactory.getInstance(); diff --git a/test/org/rascalmpl/test/functionality/ParallelEvaluatorsTests.java b/test/org/rascalmpl/test/functionality/ParallelEvaluatorsTests.java index 15134bb00bb..384422742bf 100644 --- a/test/org/rascalmpl/test/functionality/ParallelEvaluatorsTests.java +++ b/test/org/rascalmpl/test/functionality/ParallelEvaluatorsTests.java @@ -3,6 +3,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import java.io.PrintWriter; +import java.io.Reader; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; @@ -35,7 +37,7 @@ private static Evaluator freshEvaluator() { var heap = new GlobalEnvironment(); var root = heap.addModule(new ModuleEnvironment("___test___", heap)); - var evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap, monitor); + var evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), Reader.nullReader(), new PrintWriter(System.err, true), new PrintWriter(System.out), root, heap, monitor); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); evaluator.setTestResultListener(new ITestResultListener() { @@ -50,7 +52,7 @@ public void report(boolean successful, String test, ISourceLocation loc, String if (exception != null) { evaluator.warning("Got exception: " + exception, loc); - exception.printStackTrace(evaluator.getOutPrinter()); + exception.printStackTrace(evaluator.getStdOut()); throw new RuntimeException(exception); } } diff --git a/test/org/rascalmpl/test/repl/JlineParserTest.java b/test/org/rascalmpl/test/repl/JlineParserTest.java new file mode 100644 index 00000000000..91e082e1d44 --- /dev/null +++ b/test/org/rascalmpl/test/repl/JlineParserTest.java @@ -0,0 +1,68 @@ +package org.rascalmpl.test.repl; + +import static org.junit.Assert.assertEquals; + +import org.jline.reader.ParsedLine; +import org.jline.reader.Parser.ParseContext; +import org.junit.Test; +import org.rascalmpl.parser.gtd.exception.ParseError; +import org.rascalmpl.repl.jline3.RascalLineParser; +import org.rascalmpl.uri.URIUtil; + +public class JlineParserTest { + + private ParsedLine completeParser(String input) { + return completeParser(input, input.length() - 1); + } + + private ParsedLine completeParser(String input, int cursor) { + var parser = new RascalLineParser(l -> { throw new ParseError("rascal parser not supported in the test yet", URIUtil.invalidURI(), 0, 0, 0, 0, 0, 0); }); + + return parser.parse(input, cursor, ParseContext.COMPLETE); + } + + @Test + public void commandsParsedCorrectly() { + assertEquals("se", completeParser(":se").word()); + assertEquals(":", completeParser(":set prof", 2).words().get(0)); + assertEquals("set", completeParser(":set prof", 2).word()); + assertEquals("prof", completeParser(":set prof", 6).word()); + assertEquals("", completeParser(":set ", 5).word()); + } + + @Test + public void stringsAreNotParsedAsWords() { + assertEquals(1, completeParser("\"long string with multiple spaces\"").words().size()); // word + assertEquals("\"long string with multiple spaces\"", completeParser("\"long string with multiple spaces\"",11).word()); + assertEquals("\"long string with multiple spaces\"", completeParser("\"long string with multiple spaces\"").word()); + assertEquals(2, completeParser("x = \"long string with multiple spaces\";").words().size()); + } + + @Test + public void stringInterpolation() { + assertEquals("exp", completeParser("\"string string ending\"",11).words().size()); + assertEquals(3, completeParser("\"string string ending\"").words().size()); // at the end always an extra one + } + + @Test + public void qualifiedNames() { + assertEquals("IO::print", completeParser("IO::print").word()); + assertEquals("lang::rascal", completeParser("import lang::rascal").word()); + assertEquals("lang::rascal::\\syntax", completeParser("import lang::rascal::\\syntax").word()); + assertEquals("lang::rascal::\\syntax::Rascal", completeParser("import lang::rascal::\\syntax::Rascal").word()); + } + + @Test + public void locations() { + assertEquals("|", completeParser("|").word()); + assertEquals("|file", completeParser("|file").word()); + assertEquals("|file://", completeParser("|file://").word()); + assertEquals("|file:///home/dir", completeParser("|file:///home/dir").word()); + assertEquals("|file:///home/dir|", completeParser("|file:///home/dir|").word()); + } + + +}