diff --git a/src/org/rascalmpl/checker/StaticChecker.java b/src/org/rascalmpl/checker/StaticChecker.java index a781e418fd3..acb755fa842 100644 --- a/src/org/rascalmpl/checker/StaticChecker.java +++ b/src/org/rascalmpl/checker/StaticChecker.java @@ -45,7 +45,7 @@ public class StaticChecker { 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); + 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; diff --git a/src/org/rascalmpl/debug/IRascalMonitor.java b/src/org/rascalmpl/debug/IRascalMonitor.java index f60c8d9470f..124ece36592 100644 --- a/src/org/rascalmpl/debug/IRascalMonitor.java +++ b/src/org/rascalmpl/debug/IRascalMonitor.java @@ -12,9 +12,21 @@ *******************************************************************************/ package org.rascalmpl.debug; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; +import org.rascalmpl.interpreter.ConsoleRascalMonitor; +import org.rascalmpl.interpreter.NullRascalMonitor; +import org.rascalmpl.repl.IsTTY; +import org.rascalmpl.repl.TerminalProgressBarMonitor; + import io.usethesource.vallang.ISourceLocation; +import jline.TerminalFactory; public interface IRascalMonitor { /** @@ -31,11 +43,66 @@ default void jobStart(String name, int totalWork) { jobStart(name, 1, totalWork); } - default void job(String name, Supplier block) { + /** + * This utility method is not to be implemented by clients. It's a convenience + * function that helps to guarantee jobs that are started, are always ended. + */ + default T job(String name, int totalWork, Supplier block) { boolean result = false; try { - jobStart(name); - result = block.get(); + jobStart(name, totalWork); + return block.get(); + } + finally { + jobEnd(name, result); + } + } + + /** + * This utility method is not to be implemented by clients. It's a convenience + * function that helps to guarantee jobs that are started, are always ended. + * Also it provides easy access to the name of the current job, such that + * this "magic" constant does not need to be repeated or stored elsewhere. + */ + default T job(String name, int totalWork, Function block) { + boolean result = false; + try { + jobStart(name, totalWork); + return block.apply(name); + } + finally { + jobEnd(name, result); + } + } + + /** + * This utility method is not to be implemented by clients. It's a convenience + * function that helps to guarantee jobs that are started, are always ended. + * Also it provides easy access to the name of the current job, such that + * this "magic" constant does not need to be repeated or stored elsewhere. + * @param return type for the entire job + * @param name name of the job to identify the progress bar + * @param totalWork total work to be done + * @param block lambda to execute. It will get the name as a parameter + * and a `step` function to call with the current message and + * the amount of work that has been done. + * + * Example: + * ``` + * job("loading", 100, (n, step) -> { + * for (int i = 0; i < 100; i+= 10) + * doSomething() + * step("did " + i, 10); + * } + * }); + * + * @return whatever the block returns is returned by the job + */ + default T job(String name, int totalWork, BiFunction, T> block) { + boolean result = false; + try { + jobStart(name, totalWork); + return block.apply(name, (msg, worked) -> jobStep(name, msg, worked)); } finally { jobEnd(name, result); @@ -45,6 +112,10 @@ default void job(String name, Supplier block) { /** * Log the start of an event with the amount of work that will be done when it's finished. * An event is finished when the next event is logged, or when endJob() is called. + * + * jobSteps should be _ignored_ for jobs that have not started. This helps with modularizing + * the monitoring of steps acros complex reusable pieces algorithms. If the context registers + * a job, progress is monitored, otherwise it is not shown. */ public void jobStep(String name, String message, int workShare); @@ -72,8 +143,33 @@ default void jobStep(String name, String message) { */ public void jobTodo(String name, int work); + /** + * Remove all active jobs from the monitor + */ + public void endAllJobs(); + /** * Inform (about a warning */ public void warning(String message, ISourceLocation src); + + /** + * Convenience method will produce a monitor with ANSI progress bars if possible, + * and otherwise default to a dumn terminal console progress logger. + * @return + */ + public static IRascalMonitor buildConsoleMonitor(InputStream in, OutputStream out) { + return IsTTY.isTTY() + ? new TerminalProgressBarMonitor(out, in, TerminalFactory.get()) + : new ConsoleRascalMonitor(new PrintStream(out)) + ; + } + + /** + * Convenience method will produce a monitor that eats up all events without logging + * or reporting + */ + public default IRascalMonitor buildNullMonitor() { + return new NullRascalMonitor(); + } } diff --git a/src/org/rascalmpl/ideservices/BasicIDEServices.java b/src/org/rascalmpl/ideservices/BasicIDEServices.java index e4ec100c564..f799fdecb4a 100644 --- a/src/org/rascalmpl/ideservices/BasicIDEServices.java +++ b/src/org/rascalmpl/ideservices/BasicIDEServices.java @@ -20,7 +20,7 @@ import java.nio.file.Path; import java.nio.file.Paths; -import org.rascalmpl.interpreter.ConsoleRascalMonitor; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; @@ -32,15 +32,14 @@ * */ public class BasicIDEServices implements IDEServices { - - private static ConsoleRascalMonitor monitor = new ConsoleRascalMonitor(); - private PrintWriter stderr; + private final IRascalMonitor monitor; + private final PrintWriter stderr; - public BasicIDEServices(PrintWriter stderr){ + public BasicIDEServices(PrintWriter stderr, IRascalMonitor monitor){ this.stderr = stderr; - monitor = new ConsoleRascalMonitor(); + this.monitor = monitor; } - + @Override public PrintWriter stderr() { return stderr; @@ -119,6 +118,11 @@ public int jobEnd(String name, boolean succeeded) { return monitor.jobEnd(name, succeeded); } + @Override + public void endAllJobs() { + monitor.endAllJobs(); + } + @Override public boolean jobIsCanceled(String name) { return monitor.jobIsCanceled(name); @@ -133,6 +137,4 @@ public void jobTodo(String name, int work) { public void warning(String message, ISourceLocation src) { monitor.warning(message, src); } - - } diff --git a/src/org/rascalmpl/interpreter/ConsoleRascalMonitor.java b/src/org/rascalmpl/interpreter/ConsoleRascalMonitor.java index b9249157979..51b1932bd29 100644 --- a/src/org/rascalmpl/interpreter/ConsoleRascalMonitor.java +++ b/src/org/rascalmpl/interpreter/ConsoleRascalMonitor.java @@ -62,4 +62,9 @@ public void warning(String message, ISourceLocation src) { out.println("Warning: " + message); out.flush(); } + + @Override + public void endAllJobs() { + // ignore + } } diff --git a/src/org/rascalmpl/interpreter/DefaultTestResultListener.java b/src/org/rascalmpl/interpreter/DefaultTestResultListener.java index fab6d6bed70..1f42e0a9e6b 100644 --- a/src/org/rascalmpl/interpreter/DefaultTestResultListener.java +++ b/src/org/rascalmpl/interpreter/DefaultTestResultListener.java @@ -19,8 +19,7 @@ import io.usethesource.vallang.ISourceLocation; -public class DefaultTestResultListener implements ITestResultListener{ - private PrintWriter err; +public class DefaultTestResultListener implements ITestResultListener { private int successes; private int failures; private int errors; @@ -28,15 +27,16 @@ public class DefaultTestResultListener implements ITestResultListener{ private int ignored; private String context; private final boolean verbose; - private static char[] roller = new char[] {'|', '/', '-', '\\', '|', '/', '-', '\\', '|'}; + private PrintWriter out; - public DefaultTestResultListener(PrintWriter errorStream){ - this(errorStream, true); + public DefaultTestResultListener(PrintWriter out) { + this(out, true); } - public DefaultTestResultListener(PrintWriter errorStream, boolean verbose){ + + public DefaultTestResultListener(PrintWriter out, boolean verbose){ super(); - this.err = errorStream; + this.out = out; this.verbose = verbose; reset(); } @@ -49,7 +49,7 @@ private void reset() { } public void setErrorStream(PrintWriter errorStream) { - this.err = errorStream; + this.out = errorStream; } @Override @@ -61,40 +61,28 @@ public void ignored(String test, ISourceLocation loc) { public void start(String context, int count) { this.context = context; reset(); - if (count != 0 && verbose) { - err.println("Running tests for " + context); - } this.count = count; - progress(); } - - private void progress() { - if (count > 0 && verbose) { - err.print(String.format("%s testing %d/%d ", - roller[getNumberOfTests() % roller.length], getNumberOfTests(), count)); - } - } @Override public void done() { - progress(); if (count > 0) { if (!verbose) { // make sure results are reported on a newline - err.println(); + out.println(); } - err.println("\rTest report for " + context); + out.println("\rTest report for " + context); if (errors + failures == 0) { - err.println("\tall " + (count - ignored) + "/" + count + " tests succeeded"); + out.println("\tall " + (count - ignored) + "/" + count + " tests succeeded"); } else { - err.println("\t" + successes + "/" + count + " tests succeeded"); - err.println("\t" + failures + "/" + count + " tests failed"); - err.println("\t" + errors + "/" + count + " tests threw exceptions"); + out.println("\t" + successes + "/" + count + " tests succeeded"); + out.println("\t" + failures + "/" + count + " tests failed"); + out.println("\t" + errors + "/" + count + " tests threw exceptions"); } if (ignored != 0) { - err.println("\t" + ignored + "/" + count + " tests ignored"); + out.println("\t" + ignored + "/" + count + " tests ignored"); } } } @@ -102,34 +90,27 @@ public void done() { @Override public void report(boolean successful, String test, ISourceLocation loc, String message, Throwable t) { - progress(); - if (successful) { successes++; - if (verbose) { - err.print("success \r"); - } } else if (t != null) { errors++; if (!verbose) { - err.println(); + out.println(); } - err.println("error: " + test + " @ " + ReplTextWriter.valueToString(loc)); - err.println(message); + out.println("error: " + test + " @ " + ReplTextWriter.valueToString(loc)); + out.println(message); } else { failures++; if (!verbose) { - err.println(); + out.println(); } - err.println("failure: " + test + " @ " + ReplTextWriter.valueToString(loc)); - err.println(message); + out.println("failure: " + test + " @ " + ReplTextWriter.valueToString(loc)); + out.println(message); } - - - err.flush(); + out.flush(); } diff --git a/src/org/rascalmpl/interpreter/Evaluator.java b/src/org/rascalmpl/interpreter/Evaluator.java index c9c5cf141f5..ea6338cace5 100755 --- a/src/org/rascalmpl/interpreter/Evaluator.java +++ b/src/org/rascalmpl/interpreter/Evaluator.java @@ -99,6 +99,7 @@ import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener; import org.rascalmpl.parser.uptr.UPTRNodeFactory; import org.rascalmpl.parser.uptr.action.NoActionExecutor; +import org.rascalmpl.repl.TerminalProgressBarMonitor; import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.uri.URIUtil; import org.rascalmpl.values.RascalFunctionValueFactory; @@ -121,6 +122,7 @@ import io.usethesource.vallang.type.TypeFactory; public class Evaluator implements IEvaluator>, IRascalSuspendTrigger, IRascalRuntimeInspection { + private final RascalFunctionValueFactory vf; // sharable private static final TypeFactory tf = TypeFactory.getInstance(); // always shared protected volatile Environment currentEnvt; // not sharable @@ -229,8 +231,24 @@ public void decCallNesting() { private static final Object dummy = new Object(); - public Evaluator(IValueFactory f, InputStream input, OutputStream stderr, OutputStream stdout, ModuleEnvironment scope, GlobalEnvironment heap) { - this(f, input, stderr, stdout, scope, heap, new ArrayList(Collections.singleton(Evaluator.class.getClassLoader())), new RascalSearchPath()); + /** + * Promotes the monitor to the outputstream 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()); + } + + /** + * 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) { + 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()); + setMonitor(monitor); } public Evaluator(IValueFactory vf, InputStream input, OutputStream stderr, OutputStream stdout, ModuleEnvironment scope, GlobalEnvironment heap, List classLoaders, RascalSearchPath rascalPathResolver) { @@ -316,6 +334,7 @@ public IRascalMonitor setMonitor(IRascalMonitor monitor) { interrupt = false; IRascalMonitor old = this.monitor; this.monitor = monitor; + return old; } @@ -326,6 +345,13 @@ public int jobEnd(String name, boolean succeeded) { return 0; } + @Override + public void endAllJobs() { + if (monitor != null) { + monitor.endAllJobs(); + } + } + @Override public void jobStep(String name, String msg, int inc) { if (monitor != null) @@ -774,11 +800,9 @@ public IConstructor getExpandedGrammar(IRascalMonitor monitor, ISourceLocation u ParserGenerator pgen = getParserGenerator(); String main = uri.getAuthority(); ModuleEnvironment env = getHeap().getModule(main); - monitor.jobStart("Expanding Grammar"); return pgen.getExpandedGrammar(monitor, main, env.getSyntaxDefinition()); } finally { - monitor.jobEnd("Expanding Grammar", true); setMonitor(old); } } @@ -804,14 +828,12 @@ public ParserGenerator getParserGenerator() { } Evaluator self = this; - job("Loading parser generator", () -> { - synchronized (self) { - if (parserGenerator == null) { - parserGenerator = new ParserGenerator(getMonitor(), getStdErr(), classLoaders, getValueFactory(), config); - } - return true; + + synchronized (self) { + if (parserGenerator == null) { + parserGenerator = new ParserGenerator(getMonitor(), (monitor instanceof TerminalProgressBarMonitor) ? (OutputStream) getMonitor() : getStdErr(), classLoaders, getValueFactory(), config); } - }); + } } return parserGenerator; @@ -1091,7 +1113,19 @@ private Result eval(Command command) { profilerRunning = true; } try { - return command.interpret(this); + if (command.isImport()) { + return job(LOADING_JOB_CONSTANT, 1, (jobName, step) -> { + try { + return command.interpret(this); + } + finally{ + step.accept(jobName, 1); + } + }); + } + else { + return command.interpret(this); + } } finally { if (profiler != null) { profiler.pleaseStop(); @@ -1146,61 +1180,53 @@ 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()); IRascalMonitor old = setMonitor(wrapped); + try { Set onHeap = new HashSet<>(); Set extendingModules = new HashSet<>(); PrintWriter errStream = getErrorPrinter(); - - try { - monitor.jobStart("Cleaning modules", names.size()); - for (String mod : names) { - if (heap.existsModule(mod)) { - onHeap.add(mod); - if (recurseToExtending) { - extendingModules.addAll(heap.getExtendingModules(mod)); - } - heap.removeModule(heap.getModule(mod)); + + for (String mod : names) { + if (heap.existsModule(mod)) { + onHeap.add(mod); + if (recurseToExtending) { + extendingModules.addAll(heap.getExtendingModules(mod)); } - monitor.jobStep("Cleaning modules", "Processed " + mod, 1); + heap.removeModule(heap.getModule(mod)); } - extendingModules.removeAll(names); - } finally { - monitor.jobEnd("Cleaning modules", true); } + extendingModules.removeAll(names); - try { - monitor.jobStart("Reloading modules", onHeap.size()); + job(LOADING_JOB_CONSTANT, onHeap.size(), (jobName, step) -> { for (String mod : onHeap) { - wrapped.clear(); - if (!heap.existsModule(mod)) { - errStream.println("Reloading module " + mod); - reloadModule(mod, errorLocation, affectedModules); + try { + wrapped.clear(); if (!heap.existsModule(mod)) { - // something went wrong with reloading, let's print that: - errStream.println("** Something went wrong while reloading module " + mod + ":"); - for (String s : wrapped.getWarnings()) { - errStream.println(s); + reloadModule(mod, errorLocation, affectedModules, jobName); + if (!heap.existsModule(mod)) { + // something went wrong with reloading, let's print that: + errStream.println("** Something went wrong while reloading module " + mod + ":"); + for (String s : wrapped.getWarnings()) { + errStream.println(s); + } + errStream.println("*** Note: after fixing the error, you will have to manually reimport the modules that you already imported."); + errStream.println("*** if the error persists, start a new console session."); } - errStream.println("*** Note: after fixing the error, you will have to manually reimport the modules that you already imported."); - errStream.println("*** if the error persists, start a new console session."); - } - } - monitor.jobStep("Reloading modules", "loaded " + mod, 1); + } + } finally { + step.accept(mod, 1); + } } - } finally { - monitor.jobEnd("Reloading modules", true); - } - Set dependingImports = new HashSet<>(); - Set dependingExtends = new HashSet<>(); - dependingImports.addAll(getImportingModules(names)); - dependingExtends.addAll(getExtendingModules(names)); + Set dependingImports = new HashSet<>(); + Set dependingExtends = new HashSet<>(); + dependingImports.addAll(getImportingModules(names)); + dependingExtends.addAll(getExtendingModules(names)); - try { - monitor.jobStart("Reconnecting importers of affected modules"); for (String mod : dependingImports) { ModuleEnvironment env = heap.getModule(mod); Set todo = new HashSet<>(env.getImports()); + for (String imp : todo) { if (names.contains(imp)) { env.unImport(imp); @@ -1214,20 +1240,13 @@ private void reloadModules(IRascalMonitor monitor, Set names, ISourceLoc warning("could not reimport " + imp, errorLocation); } } - } - monitor.jobStep("Reconnecting importers of affected modules", "Reconnected " + mod, 1); } - } - finally { - monitor.jobEnd("Reconnecting importers of affected modules", true); - } - try { - monitor.jobStart("Reconnecting extenders of affected modules"); for (String mod : dependingExtends) { ModuleEnvironment env = heap.getModule(mod); Set todo = new HashSet<>(env.getExtends()); + for (String ext : todo) { if (names.contains(ext)) { env.unExtend(ext); @@ -1241,27 +1260,25 @@ private void reloadModules(IRascalMonitor monitor, Set names, ISourceLoc } } } - monitor.jobStep("Reconnecting extenders of affected modules", "Reconnected " + mod, 1); } - } - finally { - monitor.jobEnd("Reconnecting extenders of affected modules", true); - } - if (recurseToExtending && !extendingModules.isEmpty()) { - reloadModules(monitor, extendingModules, errorLocation, false, affectedModules); - } + if (recurseToExtending && !extendingModules.isEmpty()) { + reloadModules(monitor, extendingModules, errorLocation, false, affectedModules); + } - if (!names.isEmpty()) { - notifyConstructorDeclaredListeners(); - } + if (!names.isEmpty()) { + notifyConstructorDeclaredListeners(); + } + + return true; + }); } finally { setMonitor(old); } } - private void reloadModule(String name, ISourceLocation errorLocation, Set reloaded) { + private void reloadModule(String name, ISourceLocation errorLocation, Set reloaded, String jobName) { try { org.rascalmpl.semantics.dynamic.Import.loadModule(errorLocation, name, this); reloaded.add(name); @@ -1394,14 +1411,14 @@ private char[] getResourceContent(ISourceLocation location) throws IOException{ * effect of declaring non-terminal types in the given environment. */ @Override - public ITree parseModuleAndFragments(IRascalMonitor monitor, ISourceLocation location) throws IOException{ - return parseModuleAndFragments(monitor, getResourceContent(location), location); + public ITree parseModuleAndFragments(IRascalMonitor monitor, ISourceLocation location, String jobName) throws IOException{ + return parseModuleAndFragments(monitor, jobName, getResourceContent(location), location); } - public ITree parseModuleAndFragments(IRascalMonitor monitor, char[] data, ISourceLocation location){ + public ITree parseModuleAndFragments(IRascalMonitor monitor, String jobName, char[] data, ISourceLocation location){ IRascalMonitor old = setMonitor(monitor); try { - return org.rascalmpl.semantics.dynamic.Import.parseModuleAndFragments(data, location, this); + return org.rascalmpl.semantics.dynamic.Import.parseModuleAndFragments(data, location, jobName, this); } finally{ setMonitor(old); @@ -1795,6 +1812,11 @@ public void jobTodo(String name, int work) { monitor.jobTodo(name, work); } + @Override + public void endAllJobs() { + monitor.endAllJobs(); + } + @Override public void warning(String message, ISourceLocation src) { warnings.add(src + ":" + message); diff --git a/src/org/rascalmpl/interpreter/IEvaluator.java b/src/org/rascalmpl/interpreter/IEvaluator.java index fea8f7a17f0..1a759244ea5 100644 --- a/src/org/rascalmpl/interpreter/IEvaluator.java +++ b/src/org/rascalmpl/interpreter/IEvaluator.java @@ -53,6 +53,7 @@ * Interface needs to be properly split up in different compoments. */ public interface IEvaluator extends IEvaluatorContext { + public static final String LOADING_JOB_CONSTANT = "loading modules"; /** * Notify subscribers about a suspension caused while interpreting the program. @@ -118,7 +119,7 @@ public interface IEvaluator extends IEvaluatorContext { public ITree parseCommand(IRascalMonitor monitor, String command, ISourceLocation location); - public ITree parseModuleAndFragments(IRascalMonitor monitor, ISourceLocation location) throws IOException; + public ITree parseModuleAndFragments(IRascalMonitor monitor, ISourceLocation location, String jobName) throws IOException; public void registerConstructorDeclaredListener(IConstructorDeclared iml); diff --git a/src/org/rascalmpl/interpreter/ITestResultListener.java b/src/org/rascalmpl/interpreter/ITestResultListener.java index 2e853d2c03a..ece7dff8ec1 100644 --- a/src/org/rascalmpl/interpreter/ITestResultListener.java +++ b/src/org/rascalmpl/interpreter/ITestResultListener.java @@ -14,7 +14,7 @@ import io.usethesource.vallang.ISourceLocation; -public interface ITestResultListener{ +public interface ITestResultListener { void start(String context, int count); void report(boolean successful, String test, ISourceLocation loc, String message, Throwable exception); void ignored(String test, ISourceLocation loc); diff --git a/src/org/rascalmpl/interpreter/JavaToRascal.java b/src/org/rascalmpl/interpreter/JavaToRascal.java index f1a0194b1ae..11c387af6ca 100644 --- a/src/org/rascalmpl/interpreter/JavaToRascal.java +++ b/src/org/rascalmpl/interpreter/JavaToRascal.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.Hashtable; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.interpreter.env.Environment; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; @@ -63,7 +64,7 @@ public Evaluator getEvaluator() { public JavaToRascal(InputStream input, OutputStream stdout, OutputStream stderr) { this.evaluator = new Evaluator(vf, input, stderr, stdout, - new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap), heap); + new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap), heap, IRascalMonitor.buildConsoleMonitor(input, stdout)); } public JavaToRascal(Evaluator evaluator) { diff --git a/src/org/rascalmpl/interpreter/NullRascalMonitor.java b/src/org/rascalmpl/interpreter/NullRascalMonitor.java index 00243663108..40353bbb855 100644 --- a/src/org/rascalmpl/interpreter/NullRascalMonitor.java +++ b/src/org/rascalmpl/interpreter/NullRascalMonitor.java @@ -41,4 +41,9 @@ public boolean jobIsCanceled(String name) { public void warning(String message, ISourceLocation src) { return; } + + @Override + public void endAllJobs() { + // nothing + } } diff --git a/src/org/rascalmpl/interpreter/TestEvaluator.java b/src/org/rascalmpl/interpreter/TestEvaluator.java index bd0fb404449..d8773658029 100644 --- a/src/org/rascalmpl/interpreter/TestEvaluator.java +++ b/src/org/rascalmpl/interpreter/TestEvaluator.java @@ -87,61 +87,67 @@ public static int readIntTag(AbstractFunction test, String key, int defaultVal) private void runTests(ModuleEnvironment env, List tests) { testResultListener.start(env.getName(), tests.size()); - // first, let's shuffle the tests - tests = new ArrayList<>(tests); // just to be sure, clone the list - Collections.shuffle(tests); - - QuickCheck qc = new QuickCheck(new Random(), eval.__getVf()); - for (AbstractFunction test: tests) { - if (test.hasTag("ignore") || test.hasTag("Ignore") || test.hasTag("ignoreInterpreter") || test.hasTag("IgnoreInterpreter")) { - testResultListener.ignored(test.getName(), test.getAst().getLocation()); - continue; - } + eval.job("Testing " + env.getName(), tests.size(), (String jn) -> { + // first, let's shuffle the tests + var theTests = new ArrayList<>(tests); // just to be sure, clone the list + Collections.shuffle(theTests); + + QuickCheck qc = new QuickCheck(new Random(), eval.__getVf()); + for (AbstractFunction test: theTests) { + eval.jobStep(jn, "Running " + env.getName() + "::" + test.getName(), 1); + + if (test.hasTag("ignore") || test.hasTag("Ignore") || test.hasTag("ignoreInterpreter") || test.hasTag("IgnoreInterpreter")) { + testResultListener.ignored(test.getName(), test.getAst().getLocation()); + continue; + } - try{ - int maxDepth = readIntTag(test, QuickCheck.MAXDEPTH, 5); - int maxWidth = readIntTag(test, QuickCheck.MAXWIDTH, 5); - int tries = readIntTag(test, QuickCheck.TRIES, 500); - String expected = null; - if(test.hasTag(QuickCheck.EXPECT_TAG)){ - expected = ((IString) test.getTag(QuickCheck.EXPECT_TAG)).getValue(); - } - TestResult result = qc.test(test.getEnv().getName() + "::" + test.getName(), test.getFormals(), expected, (Type[] actuals, IValue[] args) -> { - try { - IValue testResult = test.call(actuals, args, null).getValue(); - if ((testResult instanceof IBool) && ((IBool)testResult).getValue()) { - return QuickCheck.SUCCESS; + try{ + int maxDepth = readIntTag(test, QuickCheck.MAXDEPTH, 5); + int maxWidth = readIntTag(test, QuickCheck.MAXWIDTH, 5); + int tries = readIntTag(test, QuickCheck.TRIES, 500); + String expected = null; + if(test.hasTag(QuickCheck.EXPECT_TAG)){ + expected = ((IString) test.getTag(QuickCheck.EXPECT_TAG)).getValue(); + } + TestResult result = qc.test(test.getEnv().getName() + "::" + test.getName(), test.getFormals(), expected, (Type[] actuals, IValue[] args) -> { + try { + IValue testResult = test.call(actuals, args, null).getValue(); + if ((testResult instanceof IBool) && ((IBool)testResult).getValue()) { + return QuickCheck.SUCCESS; + } + else { + return new TestResult(false, null); + } } - else { - return new TestResult(false, null); + catch (Throwable e) { + // TODO: add bound type parameters + return new UnExpectedExceptionThrownResult(test.getEnv().getName() + "::" + test.getName(), actuals, Map.of(), args, e); } + }, env.getRoot().getStore(), tries, maxDepth, maxWidth); + + eval.getOutPrinter().flush(); + eval.getErrorPrinter().flush(); + + if (!result.succeeded()) { + StringWriter sw = new StringWriter(); + PrintWriter out = new PrintWriter(sw); + result.writeMessage(out); + out.flush(); + testResultListener.report(false, test.getName(), test.getAst().getLocation(), sw.getBuffer().toString(), result.thrownException()); + } else { + testResultListener.report(true, test.getName(), test.getAst().getLocation(), "test succeeded", null); } - catch (Throwable e) { - // TODO: add bound type parameters - return new UnExpectedExceptionThrownResult(test.getEnv().getName() + "::" + test.getName(), actuals, Map.of(), args, e); - } - }, env.getRoot().getStore(), tries, maxDepth, maxWidth); + } + catch (Throwable e) { + testResultListener.report(false, test.getName(), test.getAst().getLocation(), e.getMessage(), e); + } eval.getOutPrinter().flush(); eval.getErrorPrinter().flush(); - - if (!result.succeeded()) { - StringWriter sw = new StringWriter(); - PrintWriter out = new PrintWriter(sw); - result.writeMessage(out); - out.flush(); - testResultListener.report(false, test.getName(), test.getAst().getLocation(), sw.getBuffer().toString(), result.thrownException()); - } else { - testResultListener.report(true, test.getName(), test.getAst().getLocation(), "test succeeded", null); - } - } - catch (Throwable e) { - testResultListener.report(false, test.getName(), test.getAst().getLocation(), e.getMessage(), e); } - - eval.getOutPrinter().flush(); - eval.getErrorPrinter().flush(); - } + + return true; + }); testResultListener.done(); } } diff --git a/src/org/rascalmpl/library/lang/box/util/Box2Text.rsc b/src/org/rascalmpl/library/lang/box/util/Box2Text.rsc index 3363219aa7f..3bd98ed54c8 100644 --- a/src/org/rascalmpl/library/lang/box/util/Box2Text.rsc +++ b/src/org/rascalmpl/library/lang/box/util/Box2Text.rsc @@ -68,7 +68,6 @@ import util::Math; import List; import String; import lang::box::\syntax::Box; -import IO; @synopsis{Converts boxes into a string by finding an "optimal" two-dimensional layout} @description{ diff --git a/src/org/rascalmpl/library/lang/rascal/grammar/ParserGenerator.rsc b/src/org/rascalmpl/library/lang/rascal/grammar/ParserGenerator.rsc index f19c70422a6..74839350d67 100644 --- a/src/org/rascalmpl/library/lang/rascal/grammar/ParserGenerator.rsc +++ b/src/org/rascalmpl/library/lang/rascal/grammar/ParserGenerator.rsc @@ -42,27 +42,27 @@ str getParserMethodName(conditional(Symbol s, _)) = getParserMethodName(s); default str getParserMethodName(Symbol s) = value2id(s); public str newGenerate(str package, str name, Grammar gr) { - JOB = "Generating parser ."; - jobStart("Generating parser ."); + str src = ""; + job("Generating ", void (void (str m, int w) worked) { int uniqueItem = 1; // -1 and -2 are reserved by the SGTDBF implementation int newItem() { uniqueItem += 1; return uniqueItem; }; - jobStep(JOB, "expanding parameterized symbols"); + worked("expanding parameterized symbols", 1); gr = expandParameterizedSymbols(gr); - jobStep(JOB, "generating stubs for regular"); + worked("generating stubs for regular", 1); gr = makeRegularStubs(gr); - jobStep(JOB, "generating syntax for holes"); + worked("generating syntax for holes", 1); gr = addHoles(gr); - jobStep(JOB, "generating literals"); + worked("generating literals", 1); gr = literals(gr); - jobStep(JOB, "establishing production set"); + worked("establishing production set", 1); uniqueProductions = {p | /Production p := gr, prod(_,_,_) := p || regular(_) := p}; - jobStep(JOB, "assigning unique ids to symbols"); + worked("assigning unique ids to symbols", 1); Production rewrite(Production p) = visit (p) { case Symbol s => s[id=newItem()] @@ -70,22 +70,22 @@ public str newGenerate(str package, str name, Grammar gr) { beforeUniqueGr = gr; gr.rules = (s : rewrite(gr.rules[s]) | s <- gr.rules); - jobStep(JOB, "generating item allocations"); + worked("generating item allocations", 1); newItems = generateNewItems(gr); - jobStep(JOB, "computing priority and associativity filter"); + worked("computing priority and associativity filter", 1); rel[int parent, int child] dontNest = computeDontNests(newItems, beforeUniqueGr, gr); // this creates groups of children that forbidden below certain parents rel[set[int] children, set[int] parents] dontNestGroups = { | rel[set[int] children, int parent] g := { | p <- dontNest.parent}, c <- g.children}; - //println("computing lookahead sets"); + //println("computing lookahead sets", 1); //gr = computeLookaheads(gr, extraLookaheads); - //println("optimizing lookahead automaton"); + //println("optimizing lookahead automaton", 1); //gr = compileLookaheads(gr); - jobStep(JOB, "printing the source code of the parser class"); + worked("source code template", 1); src = "package ; ' @@ -238,8 +238,8 @@ public str newGenerate(str package, str name, Grammar gr) { ' ' <}> '}"; - jobEnd(JOB); - return src; + }, totalWork=9); + return src; } rel[int,int] computeDontNests(Items items, Grammar grammar, Grammar uniqueGrammar) { diff --git a/src/org/rascalmpl/library/lang/rascal/grammar/definition/Priorities.rsc b/src/org/rascalmpl/library/lang/rascal/grammar/definition/Priorities.rsc index b492d3734d8..5c5bfa51e4e 100644 --- a/src/org/rascalmpl/library/lang/rascal/grammar/definition/Priorities.rsc +++ b/src/org/rascalmpl/library/lang/rascal/grammar/definition/Priorities.rsc @@ -25,7 +25,6 @@ data Associativity = prio(); public alias Extracted = rel[Production father, Associativity rule, Production child]; public alias DoNotNest = rel[Production father, int position, Production child]; - @synopsis{Extract which productions are not to be nested under which other productions, at given recursive positions in the parents' defining symbols list.} @description{ @@ -105,26 +104,27 @@ public DoNotNest doNotNest(Grammar g) { , same(ss, rr), same(t, lr), same(ss, t)} ; - // and we warn about recursive productions which have been left ambiguous: - allProds = {p | /p:prod(_,_,_) := g.rules[s]}; - ambiguous = { | p:prod(Symbol ss, [Symbol lr, *_], _) <- allProds, same(s, lr), - q:prod(Symbol t, [*_, Symbol rr], _) <- allProds, - same(t, rr), same(ss, t)}; - ambiguous += { | p:prod(Symbol ss, [*_, Symbol rr], _) <- allProds, same(s, rr), - q:prod(Symbol t, [Symbol lr, *_], _) <- allProds, - same(t, lr), same(ss, t), notin ambiguous} - ; + // and we warn about recursive productions which have been left ambiguous: + // TODO: this analysis can be done statically in the type-checker + // allProds = {p | /p:prod(_,_,_) := g.rules[s]}; + // ambiguous = { | p:prod(Symbol ss, [Symbol lr, *_], _) <- allProds, same(s, lr), + // q:prod(Symbol t, [*_, Symbol rr], _) <- allProds, + // same(t, rr), same(ss, t)}; + // ambiguous += { | p:prod(Symbol ss, [*_, Symbol rr], _) <- allProds, same(s, rr), + // q:prod(Symbol t, [Symbol lr, *_], _) <- allProds, + // same(t, lr), same(ss, t), notin ambiguous} + // ; - ambiguous -= (prios + prios<1,0>); // somehow the pairs are ordered - ambiguous -= (groups + groups<1,0>); // somehow the pairs are associative + // ambiguous -= (prios + prios<1,0>); // somehow the pairs are ordered + // ambiguous -= (groups + groups<1,0>); // somehow the pairs are associative // TODO extract checking into separate function - for ( <- ambiguous) { - if (p == q) - println("warning, ambiguity predicted: lacks left or right associativity"); - else - println("warning, ambiguity predicted: and lack left or right associativity or priority (\>)"); - } + // for ( <- ambiguous) { + // if (p == q) + // jobWarning("warning, ambiguity predicted: lacks left or right associativity", |unknown:///|); + // else + // jobWarning("warning, ambiguity predicted: and lack left or right associativity or priority (\>)", |unknown:///|); + // } } return result + {*except(p, g) | /Production p <- g, p is prod || p is regular}; diff --git a/src/org/rascalmpl/library/lang/std/ANSI.rsc b/src/org/rascalmpl/library/lang/std/ANSI.rsc new file mode 100644 index 00000000000..7f3d8c90401 --- /dev/null +++ b/src/org/rascalmpl/library/lang/std/ANSI.rsc @@ -0,0 +1,104 @@ +@synopsis{Documents a part of the ANSI standard for terminal control sequences} +module lang::std::ANSI + +lexical ControlCode + = bell : [\a07] + | backspace : [\a07] + | tab : [\a09] + | lineFeed : [\a0A] + | formFeed : [\a0C] + | carriageReturn: [\a0D] + | escape : [\a1B] + ; + +lexical ControlSequenceIntroducer = [\a1B] [\[]; + +lexical SpecialControlSequence = controlSequence: ControlSequenceIntroducer csi CSISequence code; + +lexical CSISequence + = cursorUp : Number n [A] + | cursorDown : Number n [B] + | cursorForward : Number n [C] + | cursorBack : Number n [D] + | cursorNextLine : Number n [E] + | cursorPreviousLine : Number n [F] + | cursorHorizontalAbsolute : Number n [G] + | cursorPosition : Number n [;] Number m [H] + | clearToEndOfScreen : Number n [0]? [J] + | clearToBeginningOfScreen : Number n [1] [J] + | clearEntireScreen : Number n [2] [J] + | clearEntireScreenAndBuffer : Number n [3] [J] + | clearToEndOfLine : "0" [K] + | clearToBeginningOfLine : "1" [K] + | clearEntireLine : "2" [K] + | scrollUp : Number n [S] + | scrollDown : Number n [T] + | horizontalVerticalPosition : Number n [;] Number m [f] + | selectGraphicsRendition : Rendition r [m] + | hideCursor : "?25l" + | showCursor : "?25h" + ; + +lexical Rendition + = reset : "0" + | bold : "1" + | faint : "2" + | italic : "3" + | underline : "4" + | slowBlink : "5" + | rapidBlink : "6" + | reverse : "7" + | conceal : "8" + | crossedOut : "9" + | primaryFont : "10" + | alternativeFont : "1" FontChoice + | fraktur : "20" + | doubleUnderline : "21" + | normalIntensity : "22" + | neitherItalicNorBlackletter : "23" + | notUnderlined : "24" + | notBlinking : "25" + | proportionalSpacing : "26" + | notReversed : "27" + | notConcealed : "28" + | notCrossedOut : "29" + | setForegroundColor : "3" ColorChoice + | setForegroundColorRGB : "38" "5" [;] Number color + | setForegroundColorRGB : "38" "2" [;] Number red [;] Number green [;] Number blue + | setDefaultForegroundColor : "39" + | setBackgroundColor : "4" ColorChoice + | setBackgroundColorRGB : "48" "5" [;] Number color + | setBackgroundColorRGB : "48" "2" [;] Number red [;] Number green [;] Number blue + | setDefaultBackgroundColor : "49" + | disableProportionalSpacing : "50" + | framed : "51" + | encircled : "52" + | overlined : "53" + | neitherFramedNorEncircled : "54" + | notOverlined : "55" + | setUnderlineColor : "58" "5" [;] Number color + | setUnderlineColor : "58" "2" [;] Number red [;] Number green [;] Number blue + | defaultUnderlineColor : "59" + | rightsideLine : "60" + | rightsideDoubleLine : "61" + | leftsideLine : "62" + | leftsideDoubleLine : "63" + | stressMarking : "64" + | resetAllLines : "65" + | superscript : "73" + | subscript : "74" + | neitherSubscriptNorSuperScript: "75" + | setBrightForegroundColor : "9" ColorChoice + | setBrightBackgroundColor : "10" ColorChoice + ; + +lexical Number = [0-9]+; +lexical ColorChoice = [0-7]; +lexical FontChoice = [0-9]; + +@synopsis{Convenience function to construct a list of digits} +Number number(int n) = [Number] ""; + +ColorChoice colorChoice(int n) = [ColorChoice] "" when 0 <= n, n <= 7; + +FontChoice fontChoice(int n) = [FontChoice] "" when 0 <= n, n <= 9; \ No newline at end of file diff --git a/src/org/rascalmpl/library/util/Eval.java b/src/org/rascalmpl/library/util/Eval.java index f3d69b88929..b680b6475e0 100644 --- a/src/org/rascalmpl/library/util/Eval.java +++ b/src/org/rascalmpl/library/util/Eval.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.Map; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.exceptions.RuntimeExceptionFactory; import org.rascalmpl.exceptions.Throw; import org.rascalmpl.interpreter.Evaluator; @@ -65,7 +66,7 @@ public class Eval { - public Eval(IValueFactory values, OutputStream out, OutputStream err, InputStream in, ClassLoader loader) { + public Eval(IValueFactory values, OutputStream out, OutputStream err, InputStream in, ClassLoader loader, IRascalMonitor monitor) { super(); this.values = values; this.tr = new TypeReifier(values); @@ -73,7 +74,7 @@ public Eval(IValueFactory values, OutputStream out, OutputStream err, InputStrea GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = new ModuleEnvironment("$eval$", heap); - this.eval = new Evaluator(values, in, err, out, root, heap); + this.eval = new Evaluator(values, in, err, out, root, heap, monitor); // TODO: this is to fix the course Question compiler with a workaround. // it would be better to parameterize eval with a PathConfig. diff --git a/src/org/rascalmpl/library/util/Monitor.rsc b/src/org/rascalmpl/library/util/Monitor.rsc index 8f0a93e58ee..3d46000e073 100644 --- a/src/org/rascalmpl/library/util/Monitor.rsc +++ b/src/org/rascalmpl/library/util/Monitor.rsc @@ -11,28 +11,192 @@ @bootstrapParser module util::Monitor +import util::Math; +import IO; -@synopsis{Log the __start__ of a job.} +@synopsis{Log the start of a job.} @description{ -jobStart registers a new current job on the job stack with an amount of +((jobStart)) registers a new current job on the job stack with an amount of steps todo and how much work it contributes (when it ends) to its parent job (if any). +* The `label` _identifies_ as running task, which could be presented in the UI with a specific progress bar (for example) +* Using ((jobStep)) with the same label, you can advance the amount of work done for the task. +* All tasks should eventually end with ((jobEnd)), but most UI's will clean up open left-over tasks at natural intervals as well. +* Use ((jobTodo)) to register additional dynamically discovered work for a task. The `totalWork` constant for that +specific task will be increased by the given amount. + + +} +@benefits{ +* It is adviced to use the "block" functions `job` instead of the raw `jobStart`, `jobStep` and `jobEnd` +functions because these guarantee each started task is always ended, with and without exceptions. This improves +the user experience for your users. Also these functions help by providing the job label in the scope of the task, +such that this "magic constant" does not need to be repeated. +} +@pitfalls{ +* The job label is both an identity and a user facing string constant. Future versions of the API may +split the identify from the label for better accuracy and better UI. } @javaClass{org.rascalmpl.library.util.Monitor} -public java void jobStart(str label, int work=1, int totalWork=100); +java void jobStart(str label, int work=1, int totalWork=100); -@synopsis{Log to the user that a certain event has happened under - the currently registered Job.} +@synopsis{Log to the user that a certain event has happened under the currently registered Job.} @javaClass{org.rascalmpl.library.util.Monitor} -public java void jobStep(str label, str message, int work = 1); +java void jobStep(str label, str message, int work = 1); @javaClass{org.rascalmpl.library.util.Monitor} -public java int jobEnd(str label, bool success=true); +@synopsis{Log the end of a job} +java int jobEnd(str label, bool success=true); @javaClass{org.rascalmpl.library.util.Monitor} -public java void jobTodo(str label, int work=100); +@synopsis{Register additional work for the identied job.} +java void jobTodo(str label, int work=1); @javaClass{org.rascalmpl.library.util.Monitor} -public java void jobIsCancelled(str label); +@synopsis{Poll if the given job has been cancelled by a user interaction.} +java void jobIsCancelled(str label); @javaClass{org.rascalmpl.library.util.Monitor} -public java void jobWarning(str message, loc src); +@synopsis{Register a warning in the same UI that job progress is shown in.} +java void jobWarning(str message, loc src); + +@synopsis{A job block guarantees a start and end, and provides easy access to the stepper interface.} +@description{ +The convenience function that is passed to the block can be used inside the block to register steps +with a parameterized workload and the same label as the job name. +} +@benefits{ +* the block body does not need to repeat the `name` parameter when ending the job or making steps +* the job is always properly ended, even when exceptions are thrown +} +@pitfalls{ +* additional work with ((jobTodo)) is still possible, but you have to repeat the right job label. +} +void job(str label, void (void (str message, int worked) step) block, int totalWork=100) { + try { + jobStart(label, totalWork=totalWork); + block((str message, int worked) { + jobStep(label, message, work=worked); + }); + } + catch x: { + throw x; + } + finally { + jobEnd(label); + } +} + +@synopsis{A job block guarantees a start and end, and provides easy access to the stepper interface.} +@description{ +The convenience function that is passed to the block can be used inside the block to register steps +with a parameterized workload and the same label as the job name. +} +@benefits{ +* the block body does not need to repeat the `name` parameter when ending the job or making steps +* the job is always properly ended, even when exceptions are thrown +} +@pitfalls{ +* additional work with ((jobTodo)) is still possible, but you have to repeat the right job label. +} +void job(str label, void (void (int worked) step) block, int totalWork=1) { + try { + jobStart(label, totalWork=totalWork); + block((int worked) { + jobStep(label, label, work=worked); + }); + } + catch x: { + throw x; + } + finally { + jobEnd(label); + } +} + +@synopsis{A job block guarantees a start and end, and provides easy access to the stepper interface.} +@description{ +The convenience function that is passed to the block can be used inside the block to register steps +with workload `1` and the same label as the job name. +} +@benefits{ +* the block body does not need to repeat the `name` parameter when ending the job or making steps +* the job is always properly ended, even when exceptions are thrown +} +@pitfalls{ +* additional work with ((jobTodo)) is still possible, but you have to repeat the right job label. +} +void job(str label, void (void () step) block, int totalWork=1) { + try { + jobStart(label, totalWork=totalWork); + block(() { + jobStep(label, label, work=1); + }); + } + catch x: { + throw x; + } + finally { + jobEnd(label); + } +} + +@synopsis{A job block guarantees a start and end, and provides easy access to the stepper interface.} +@benefits{ +* the block code does not need to remember to end the job with the same job name. +* the job is always properly ended, even when exceptions are thrown +} +void job(str label, void () block, int totalWork=1) { + try { + jobStart(label, totalWork=totalWork); + block(); + } + catch x: { + throw x; + } + finally { + jobEnd(label); + } +} + +@synopsis{Puts the monitor API to work by racing 5 horses against each other.} +test bool horseRaceTest() { + distance = 3000000; + stride = 50; + horses = 5; + handicaps = [ arbInt(stride * 15 / 100) | _ <- [0..horses]]; + labels = [ "Horse (handicap is )" | h <- [0..horses]]; + progress = [ 0 | _ <- [0..horses]]; + + for (int h <- [0..horses]) + jobStart(labels[h], totalWork=distance); + + race:while (true) + for(int h <- [0..horses]) { + advance = arbInt(stride - handicaps[h]); + progress[h] += advance; + + jobStep(labels[h], "Pacing horse with ...", work=advance); + + if (progress[h] >= distance) { + break race; + } + } + + for (int h <- [0..horses]) + jobEnd(labels[h]); + + return true; +} + +test bool simpleAsyncPrintTest() { + jobStart("job", totalWork=3); + println("a"); + jobStep("job", "step 1", work=1); + println("b"); + jobStep("job", "step 2", work=1); + println("c"); + jobStep("job", "step 3", work=1); + println("d"); + jobEnd("job"); + return true; +} \ No newline at end of file diff --git a/src/org/rascalmpl/library/util/Progress.rsc b/src/org/rascalmpl/library/util/Progress.rsc index 74db9e3f391..e0b64a425cb 100644 --- a/src/org/rascalmpl/library/util/Progress.rsc +++ b/src/org/rascalmpl/library/util/Progress.rsc @@ -6,6 +6,7 @@ http://www.eclipse.org/legal/epl-v10.html } @contributor{Jouke Stoel - jouke.stoel@cwi.nl - CWI} +@deprecated{Use util::Monitor for the same effect with more support for different IDEs and commandline environments.} module util::Progress import String; diff --git a/src/org/rascalmpl/library/util/Random.rsc b/src/org/rascalmpl/library/util/Random.rsc index e9e70b375e8..254b72472f6 100644 --- a/src/org/rascalmpl/library/util/Random.rsc +++ b/src/org/rascalmpl/library/util/Random.rsc @@ -1,11 +1,18 @@ +@license{ + Copyright (c) 2019 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 +} +@contributor{Davy Landman} +@contributor{Jurgen J. Vinju} module util::Random - @synopsis{Get a random value of a certain type} @javaClass{org.rascalmpl.library.Prelude} java &T randomValue(type[&T] ofType, int depth = 5, int width = 5); - @synopsis{Get a random value of a certain type} @javaClass{org.rascalmpl.library.Prelude} java &T randomValue(type[&T] ofType, int seed, int depth = 5, int width = 5); diff --git a/src/org/rascalmpl/library/util/Reflective.java b/src/org/rascalmpl/library/util/Reflective.java index 98f50a5a981..44bb23ce3b1 100644 --- a/src/org/rascalmpl/library/util/Reflective.java +++ b/src/org/rascalmpl/library/util/Reflective.java @@ -23,8 +23,8 @@ import java.io.Writer; import java.net.URISyntaxException; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.exceptions.RuntimeExceptionFactory; -import org.rascalmpl.interpreter.ConsoleRascalMonitor; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.IEvaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; @@ -69,10 +69,12 @@ public class Reflective { protected final IValueFactory values; + private final IRascalMonitor monitor; - public Reflective(IValueFactory values){ + public Reflective(IValueFactory values, IRascalMonitor monitor) { super(); this.values = values; + this.monitor = monitor; } public IString getRascalVersion() { @@ -101,9 +103,8 @@ IEvaluator getDefaultEvaluator(OutputStream stdout, OutputStream 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); + Evaluator evaluator = new Evaluator(vf, System.in, stderr, stdout, root, heap, monitor); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); - evaluator.setMonitor(new ConsoleRascalMonitor()); return evaluator; } diff --git a/src/org/rascalmpl/library/util/RunTests.java b/src/org/rascalmpl/library/util/RunTests.java index 2646545d377..4ad5ba81bca 100644 --- a/src/org/rascalmpl/library/util/RunTests.java +++ b/src/org/rascalmpl/library/util/RunTests.java @@ -43,12 +43,11 @@ public class RunTests { public RunTests(IValueFactory vf) { } - public IList runTests(IString moduleName, IEvaluatorContext ctx) { return runTests(moduleName.getValue(), (Evaluator) ctx.getEvaluator()); } - // TODO: this has to be rewritten after we finish the compiler, using some Java reflection. + // TODO: this has to be rewritten after we finish the compiler, using some Java reflection. public static IList runTests(String module, Evaluator eval) { IValueFactory vf = eval.getValueFactory(); ModuleEnvironment root = new ModuleEnvironment("***testroot***", eval.getHeap()); diff --git a/src/org/rascalmpl/library/util/Webserver.java b/src/org/rascalmpl/library/util/Webserver.java index 87a2d14ee4a..292241ed33a 100644 --- a/src/org/rascalmpl/library/util/Webserver.java +++ b/src/org/rascalmpl/library/util/Webserver.java @@ -332,7 +332,7 @@ private IMap makeMap(Map headers) { try { server.start(NanoHTTPD.SOCKET_READ_TIMEOUT, asDeamon.getValue()); servers.put(url, server); - monitor.jobStart("Server: " + server); + if (!asDeamon.getValue()) { out.println("Starting http server in non-daemon mode, hit ctrl-c to stop it"); out.flush(); @@ -349,7 +349,6 @@ private IMap makeMap(Map headers) { } server.stop(); servers.remove(url); - monitor.jobEnd("Server: " + server, true); } } catch (IOException e) { throw RuntimeExceptionFactory.io(vf.string(e.getMessage()), null, null); diff --git a/src/org/rascalmpl/parser/ParserGenerator.java b/src/org/rascalmpl/parser/ParserGenerator.java index d174bca78f5..a1f7698e26a 100644 --- a/src/org/rascalmpl/parser/ParserGenerator.java +++ b/src/org/rascalmpl/parser/ParserGenerator.java @@ -55,7 +55,7 @@ public class ParserGenerator { public ParserGenerator(IRascalMonitor monitor, OutputStream 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); + this.evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, out, out, scope, heap, monitor); this.evaluator.getConfiguration().setRascalJavaClassPathProperty(config.getRascalJavaClassPathProperty()); this.evaluator.getConfiguration().setGeneratorProfiling(config.getGeneratorProfilingProperty()); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); @@ -63,22 +63,16 @@ public ParserGenerator(IRascalMonitor monitor, OutputStream out, List> getNewParser(IRascalMonitor monitor, ISourceLocation loc, String name, IMap definition) { - String JOB = "Generating parser:" + name; - monitor.jobStart(JOB, 100, 130); - Profiler profiler = evaluator.getConfiguration().getGeneratorProfilingProperty() ? new Profiler(evaluator) : null; try { - monitor.jobStep(JOB, "Importing and normalizing grammar:" + name, 30); if (profiler != null) { profiler.start(); } IConstructor grammar = IRascalValueFactory.getInstance().grammar(definition); debugOutput(grammar, System.getProperty("java.io.tmpdir") + "/grammar.trm"); return getNewParser(monitor, loc, name, grammar); - } catch (ClassCastException e) { + } + catch (ClassCastException e) { throw new ImplementationError("parser generator:" + e.getMessage(), e); - } catch (Throw e) { + } + catch (Throw e) { throw new ImplementationError("parser generator: " + e.getMessage() + e.getTrace()); - } finally { - monitor.jobEnd(JOB, true); + } + finally { if (profiler != null) { profiler.pleaseStop(); evaluator.getOutPrinter().println("PROFILE:"); @@ -230,25 +215,20 @@ public Class> getNewParser(IRascalMon * @return A parser class, ready for instantiation */ public Class> getNewParser(IRascalMonitor monitor, ISourceLocation loc, String name, IConstructor grammar) { - String JOB = "Generating parser:" + name; - monitor.jobStart(JOB, 100, 60); - try { String normName = name.replaceAll("::", "_").replaceAll("\\\\", "_"); - monitor.jobStep(JOB, "Generating java source code for parser: " + name,30); + IString classString; synchronized (evaluator) { classString = (IString) evaluator.call(monitor, "newGenerate", vf.string(packageName), vf.string(normName), grammar); } debugOutput(classString, System.getProperty("java.io.tmpdir") + "/parser.java"); - monitor.jobStep(JOB,"Compiling generated java code: " + name, 30); + return bridge.compileJava(loc, packageName + "." + normName, classString.getValue()); } catch (ClassCastException e) { throw new ImplementationError("parser generator:" + e.getMessage(), e); } catch (Throw e) { throw new ImplementationError("parser generator: " + e.getMessage() + e.getTrace()); - } finally { - monitor.jobEnd(JOB, true); } } @@ -263,12 +243,8 @@ public Class> getNewParser(IRascalMon * @throws IOException */ public void writeNewParser(IRascalMonitor monitor, ISourceLocation loc, String name, IMap definition, ISourceLocation target) throws IOException { - String JOB = "Generating parser:" + name; - monitor.jobStart(JOB, 100, 60); - try (OutputStream out = URIResolverRegistry.getInstance().getOutputStream(target, false)) { String normName = name.replaceAll("::", "_").replaceAll("\\\\", "_"); - monitor.jobStep(JOB, "Generating java source code for parser: " + name,30); IString classString; IConstructor grammar = IRascalValueFactory.getInstance().grammar(definition); @@ -276,15 +252,12 @@ public void writeNewParser(IRascalMonitor monitor, ISourceLocation loc, String n classString = (IString) evaluator.call(monitor, "newGenerate", vf.string(packageName), vf.string(normName), grammar); } debugOutput(classString, System.getProperty("java.io.tmpdir") + "/parser.java"); - monitor.jobStep(JOB,"Compiling generated java code: " + name, 30); bridge.compileJava(loc, packageName + "." + normName, classString.getValue(), out); } catch (ClassCastException e) { throw new ImplementationError("parser generator:" + e.getMessage(), e); } catch (Throw e) { throw new ImplementationError("parser generator: " + e.getMessage() + e.getTrace()); - } finally { - monitor.jobEnd(JOB, true); } } diff --git a/src/org/rascalmpl/repl/BaseRascalREPL.java b/src/org/rascalmpl/repl/BaseRascalREPL.java index a59b48b6949..24a3177a7e3 100755 --- a/src/org/rascalmpl/repl/BaseRascalREPL.java +++ b/src/org/rascalmpl/repl/BaseRascalREPL.java @@ -103,6 +103,7 @@ public void handleInput(String line, Map output, Map bars = new LinkedList<>(); + + /** + * 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. + */ + private int lineWidth; + + private final boolean unicodeEnabled; + + /**x + * Will make everything slow, but easier to spot mistakes + */ + private final boolean debug = false; + + /** + * Used to get updates to the width of the terminal + */ + private final Terminal tm; + + private final String encoding; + + @SuppressWarnings("resource") + public TerminalProgressBarMonitor(OutputStream out, InputStream in, Terminal tm) { + super(out); + + 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); + + 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 { + + public AlwaysFlushAlwaysShowCursor(PrintWriter out) { + super(out); + } + + @Override + public void write(int c) throws IOException { + out.write(c); + out.write(ANSI.showCursor()); + out.flush(); + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + out.write(cbuf, off, len); + out.write(ANSI.showCursor()); + out.flush(); + } + + @Override + public void write(String str, int off, int len) throws IOException { + out.write(str, off, len); + out.write(ANSI.showCursor()); + out.flush(); + } + } + + /** + * Represents one currently running progress bar + */ + private class ProgressBar { + private final long threadId; + private final String threadName; + private final String name; + private int max; + private int current = 0; + private int previousWidth = 0; + private int doneWidth = 0; + private final int barWidthUnicode = lineWidth - "☐ ".length() - " 🕐 00:00:00.000 ".length(); + private final int barWidthAscii = lineWidth - "? ".length() - " - 00:00:00.000 ".length(); + private final Instant startTime; + private Duration duration; + private String message = ""; + + /** + * Stepper is incremented with every jobStep that has an visible effect on the progress bar. + * It is used to index into `clocks` or `twister` to create an animation effect. + */ + private int stepper = 1; + private final String[] clocks = new String[] {"🕐" , "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘", "🕙", "🕛"}; + private final String[] twister = new String[] {"." , ".", "o", "o", "O","O", "O", "o", "o", ".", "."}; + public int nesting = 0; + + ProgressBar(String name, int max) { + this.threadId = Thread.currentThread().getId(); + this.threadName = Thread.currentThread().getName(); + this.name = name; + this.max = Math.max(1, max); + this.startTime = Instant.now(); + this.duration = Duration.ZERO; + this.message = name; + } + + void worked(int amount, String message) { + if (current + amount > max) { + // Fixing this warning helps avoiding to flicker the tick sign on and off, and also makes the progress bar + // a more accurate depiction of the progress of the computation. + warning("Monitor of " + name + " is over max (" + max + ") by " + (current + amount - max), null); + } + + this.current = Math.min(current + amount, max); + this.duration = Duration.between(startTime, Instant.now()); + this.message = message; + } + + /** + * To avoid flickering of all bars at the same time, we only reprint + * the current bar + */ + 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))); + 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)); + } + writer.flush(); + } + } + + int newWidth() { + if (max != 0) { + current = Math.min(max, current); // for robustness sake + var partDone = (current * 1.0) / (max * 1.0); + return (int) Math.floor(barWidthUnicode * partDone); + } + else { + return barWidthUnicode % stepper; + } + } + + /** + * Print the current state of the progress bar + */ + void write() { + previousWidth = doneWidth; + doneWidth = newWidth(); + + // var overWidth = barWidth - doneWidth; + var done = unicodeEnabled + ? (current >= max ? "☑ " : "☐ ") + : (current >= max ? "X " : "O ") + ; + + // capitalize + var msg = message.substring(0, 1).toUpperCase() + message.substring(1, message.length()); + + // fill up and cut off: + msg = threadLabel() + msg; + msg = (msg + " ".repeat(Math.max(0, barWidthUnicode - msg.length()))).substring(0, barWidthUnicode); + + // split + var barWidth = unicodeEnabled ? barWidthUnicode : barWidthAscii; + var frontPart = msg.substring(0, doneWidth); + var backPart = msg.substring(doneWidth, msg.length()); + var clock = unicodeEnabled ? clocks[stepper % clocks.length] : twister[stepper % twister.length]; + + if (barWidth < 1) { + 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); + return; + } + + var line + = done + + ANSI.darkBackground() + + frontPart + + ANSI.noBackground() + + ANSI.lightBackground() + + backPart + + ANSI.noBackground() + + " " + clock + " " + + String.format("%d:%02d:%02d.%03d", duration.toHoursPart(), duration.toMinutes(), duration.toSecondsPart(), duration.toMillisPart()) + + " " + ; + + writer.println(line); // note this puts us one line down + } + + private String threadLabel() { + if (threadName.isEmpty()) { + return ""; + } + else if ("main".equals(threadName)) { + return ""; + } + + return threadName + ": "; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ProgressBar + && ((ProgressBar) obj).name.equals(name) + && ((ProgressBar) obj).threadId == threadId + ; + } + + @Override + public int hashCode() { + return name.hashCode() + 31 * (int) threadId; + } + + public void done() { + this.current = Math.min(current, max); + this.duration = Duration.between(startTime, Instant.now()); + this.message = name; + } + } + + /** + * Clean the screen, ready for the next output (normal output or the next progress bars or both). + * We use fancy ANSI codes here to move the cursor and clean the screen to remove previous versions + * of the bars + */ + private void eraseBars() { + if (!bars.isEmpty()) { + writer.write(ANSI.moveUp(bars.size())); + writer.write(ANSI.clearToEndOfScreen()); + } + writer.flush(); + } + + /** + * ANSI escape codes convenience functions + */ + private static class ANSI { + static boolean isUTF8enabled(PrintWriter writer, InputStream in) { + try { + int pos = getCursorPosition(writer, in); + writer.write("あ"); + writer.flush(); + int newPos = getCursorPosition(writer, in); + int diff = newPos - pos; + + try { + return diff == 2; + } + finally { + while (--diff >= 0) { + writer.write(ANSI.delete()); + } + writer.flush(); + } + } + catch (IOException e) { + return false; + } + } + + 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 = new String(col, 0, len, Configuration.getEncoding()); + + 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"; + } + + public static String printCursorPosition() { + return "\u001B[6n"; + } + + public static String darkBackground() { + return "\u001B[48;5;242m"; + } + + public static String noBackground() { + return "\u001B[49m"; + } + + public static String lightBackground() { + return "\u001B[48;5;249m"; + } + + static String moveDown(int n) { + return "\u001B[" + n + "E"; + } + + static String clearToEndOfScreen() { + return "\u001B[0J"; + } + + static String hideCursor() { + return "\u001B[?25l"; + } + + static String showCursor() { + return "\u001B[?25h"; + } + } + + /** + * Simply print the bars. No cursor movement here. Hiding the cursor prevents flickering. + */ + private void printBars() { + if (bars.isEmpty()) { + // no more bars to show, so cursor goes back. + writer.write(ANSI.showCursor()); + } + + for (var pb : bars) { + pb.write(); + } + + writer.flush(); + } + + /** + * Find a bar in the ordered list of bars, by name. + * @param name of the bar + * @return the current instance by that name + */ + private ProgressBar findBarByName(String name) { + return bars.stream() + .filter(b -> b.threadId == Thread.currentThread().getId()) + .filter(b -> b.name.equals(name)).findFirst().orElseGet(() -> null); + } + + @Override + public synchronized void jobStart(String name, int workShare, int totalWork) { + if (bars.size() == 0) { + // first new job, we take time to react to window resizing + lineWidth = tm.getWidth(); + // remove the cursor + writer.write(ANSI.hideCursor()); + } + + var pb = findBarByName(name); + + if (pb == null) { + eraseBars(); // to make room for the new bars + bars.add(new ProgressBar(name, totalWork)); + printBars(); // probably one line longer than before! + } + else { + // Zeno-bar: we add the new work to the already existing work + pb.max += totalWork; + pb.nesting++; + pb.update(); + } + } + + @Override + public synchronized void jobStep(String name, String message, int workShare) { + ProgressBar pb = findBarByName(name); + + if (pb != null) { + pb.worked(workShare, message); + pb.update(); + } + } + + @Override + public synchronized int jobEnd(String name, boolean succeeded) { + var pb = findBarByName(name); + + if (pb != null && --pb.nesting == -1) { + eraseBars(); + // write it one last time into the scrollback buffer (on top) + pb.done(); + pb.write(); + bars.remove(pb); + // print the left over bars under this one. + printBars(); + return pb.current; + } + else if (pb != null) { + pb.done(); + pb.update(); + } + + return -1; + } + + @Override + public synchronized boolean jobIsCanceled(String name) { + // ? don't know what this should do + return false; + } + + @Override + public synchronized void jobTodo(String name, int work) { + ProgressBar pb = findBarByName(name); + + if (pb != null) { + pb.max += work; + pb.update(); + } + } + + @Override + public synchronized void warning(String message, ISourceLocation src) { + if (bars.size() > 0) { + eraseBars(); + } + writer.println(("[WARNING] " + (src != null ? (src + ": ") : "") + message)); + if (bars.size() > 0) { + printBars(); + } + } + + /** + * 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) throws IOException { + if (bars.size() > 0) { + eraseBars(); + out.write(b); + printBars(); + } + else { + out.write(b); + } + } + + /** + * 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 { + if (bars.size() > 0) { + eraseBars(); + out.write(b, off, len); + printBars(); + } + else { + out.write(b, 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(int b) throws IOException { + if (bars.size() > 0) { + eraseBars(); + out.write(b); + printBars(); + } + else { + out.write(b); + } + } + + @Override + public synchronized void endAllJobs() { + for (var pb : bars) { + if (pb.nesting >= 0) { + writer.println("[INFO] " + pb.name + " is still at nesting level " + pb.nesting); + } + } + + bars.clear(); + writer.write(ANSI.showCursor()); + writer.flush(); + } +} diff --git a/src/org/rascalmpl/semantics/dynamic/Import.java b/src/org/rascalmpl/semantics/dynamic/Import.java index 80fff9028be..ebb4656494a 100644 --- a/src/org/rascalmpl/semantics/dynamic/Import.java +++ b/src/org/rascalmpl/semantics/dynamic/Import.java @@ -293,55 +293,60 @@ else if (eval.getCurrentEnvt() == eval.__getRootScope()) { public static ModuleEnvironment loadModule(ISourceLocation x, String name, IEvaluator> eval) { GlobalEnvironment heap = eval.getHeap(); + String jobName = IEvaluator.LOADING_JOB_CONSTANT; - ModuleEnvironment env = heap.getModule(name); - if (env == null) { - env = new ModuleEnvironment(name, heap); - heap.addModule(env); + ModuleEnvironment m = heap.getModule(name); + if (m == null) { + m = new ModuleEnvironment(name, heap); + heap.addModule(m); } + final ModuleEnvironment env = m; - try { - ISourceLocation uri = eval.getRascalResolver().resolveModule(name); - if (uri == null) { - throw new ModuleImport(name, "can not find in search path", x); - } - Module module = buildModule(uri, env, eval); + ISourceLocation uri = eval.getRascalResolver().resolveModule(name); - if (isDeprecated(module)) { - eval.getErrorPrinter().println("WARNING: deprecated module " + name + ":" + getDeprecatedMessage(module)); - } + try { + eval.jobTodo(jobName, 1); + if (uri == null) { + throw new ModuleImport(name, "can not find in search path", x); + } + Module module = buildModule(uri, env, eval, jobName); - if (module != null) { - String internalName = org.rascalmpl.semantics.dynamic.Module.getModuleName(module); - if (!internalName.equals(name)) { - throw new ModuleNameMismatch(internalName, name, x); - } - heap.setModuleURI(name, module.getLocation().getURI()); + if (isDeprecated(module)) { + eval.getErrorPrinter().println("WARNING: deprecated module " + name + ":" + getDeprecatedMessage(module)); + } - module.interpret(eval); + if (module != null) { + String internalName = org.rascalmpl.semantics.dynamic.Module.getModuleName(module); + if (!internalName.equals(name)) { + throw new ModuleNameMismatch(internalName, name, x); + } + heap.setModuleURI(name, module.getLocation().getURI()); - return env; - } - } - catch (SyntaxError e) { - handleLoadError(heap, env, eval, name, e.getMessage(), e.getLocation(), x); - throw e; - } - catch (StaticError e) { - handleLoadError(heap, env, eval, name, e.getMessage(), e.getLocation(), x); - throw e; - } - catch (Throw e) { - handleLoadError(heap, env, eval, name, e.getMessage(), e.getLocation(), x); - throw e; - } catch (Throwable e) { - handleLoadError(heap, env, eval, name, e.getMessage(), x, x); - e.printStackTrace(); - throw new ModuleImport(name, e.getMessage(), x); - } - - heap.removeModule(env); - throw new ImplementationError("Unexpected error while parsing module " + name + " and building an AST for it ", x); + module.interpret(eval); + } + } + catch (SyntaxError e) { + handleLoadError(heap, env, eval, name, e.getMessage(), e.getLocation(), x); + throw e; + } + catch (StaticError e) { + handleLoadError(heap, env, eval, name, e.getMessage(), e.getLocation(), x); + throw e; + } + catch (Throw e) { + handleLoadError(heap, env, eval, name, e.getMessage(), e.getLocation(), x); + throw e; + } + catch (Throwable e) { + handleLoadError(heap, env, eval, name, e.getMessage(), x, x); + e.printStackTrace(); + throw new ModuleImport(name, e.getMessage(), x); + } + finally { + eval.jobStep(jobName, name, 1); + } + + return env; } private static void handleLoadError(GlobalEnvironment heap, ModuleEnvironment env, IEvaluator> eval, @@ -370,16 +375,10 @@ private static String getDeprecatedMessage(Module preModule){ return ""; } - private static Module buildModule(ISourceLocation uri, ModuleEnvironment env, IEvaluator> eval) throws IOException { - try { - eval.jobStart("Loading module " + uri, 10); - ITree tree = eval.parseModuleAndFragments(eval, uri); + private static Module buildModule(ISourceLocation uri, ModuleEnvironment env, IEvaluator> eval, String jobName) throws IOException { + ITree tree = eval.parseModuleAndFragments(eval, uri, jobName); - return getBuilder().buildModule(tree); - } - finally { - eval.jobEnd("Loading module " + uri, true); - } + return getBuilder().buildModule(tree); } private static ASTBuilder getBuilder() { @@ -396,17 +395,21 @@ private static void addImportToCurrentModule(ISourceLocation src, String name, I current.setSyntaxDefined(current.definesSyntax() || module.definesSyntax()); } - public static ITree parseModuleAndFragments(char[] data, ISourceLocation location, IEvaluator> eval) { + public static ITree parseModuleAndFragments(char[] data, ISourceLocation location, String jobName, IEvaluator> eval) { eval.__setInterrupt(false); IActionExecutor actions = new NoActionExecutor(); ITree tree; - + try { + eval.jobTodo(jobName, 1); tree = new RascalParser().parse(Parser.START_MODULE, location.getURI(), data, actions, new DefaultNodeFlattener(), new UPTRNodeFactory(true)); } catch (ParseError e) { throw new SyntaxError("module", IRascalValueFactory.getInstance().sourceLocation(location, e.getOffset(), e.getLength(), e.getBeginLine(), e.getEndLine(), e.getBeginColumn(), e.getEndColumn())); } + finally { + eval.jobStep(jobName, "parsed " + URIUtil.getLocationName(location), 1); + } if (TreeAdapter.isAmb(tree)) { // Ambiguity is dealt with elsewhere @@ -437,23 +440,31 @@ public static ITree parseModuleAndFragments(char[] data, ISourceLocation locatio eval.getCurrentModuleEnvironment().clearProductions(); ISet rules = Modules.getSyntax(top); + eval.getMonitor().jobTodo(jobName, rules.size()); for (IValue rule : rules) { evalImport(eval, (IConstructor) rule); + eval.getMonitor().jobStep(jobName, "defining syntax for " + name, 1); } ISet imports = Modules.getImports(top); + eval.getMonitor().jobTodo(jobName, imports.size()); for (IValue mod : imports) { evalImport(eval, (IConstructor) mod); + eval.getMonitor().jobStep(jobName, "importing for " + name, 1); } ISet extend = Modules.getExtends(top); + eval.getMonitor().jobTodo(jobName, extend.size()); for (IValue mod : extend) { evalImport(eval, (IConstructor) mod); + eval.getMonitor().jobStep(jobName, "extending for " + name, 1); } ISet externals = Modules.getExternals(top); + eval.getMonitor().jobTodo(jobName, externals.size()); for (IValue mod : externals) { evalImport(eval, (IConstructor) mod); + eval.getMonitor().jobStep(jobName, "external importing for " + name, 1); } } finally { @@ -487,7 +498,13 @@ else if (reg.exists(parserCacheFile)) { parsers = vf.parsers(reifiedType, vf.bool(false), vf.bool(false), vf.bool(false), vf.set()); } - result = parseFragments(vf, eval.getMonitor(), parsers, tree, location, env); + try { + eval.getMonitor().jobTodo(jobName, 1); + result = parseFragments(vf, eval.getMonitor(), parsers, tree, location, env); + } + finally { + eval.getMonitor().jobStep(jobName, "parsed concrete fragments", 1); + } } } catch (URISyntaxException | ClassNotFoundException | IOException e) { @@ -506,7 +523,7 @@ private static void declareTypesWhichDoNotNeedImportedModulesAlready(IEvaluator< public static void evalImport(IEvaluator> eval, IConstructor mod) { org.rascalmpl.ast.Import imp = (org.rascalmpl.ast.Import) getBuilder().buildValue(mod); try { - imp.interpret(eval); + imp.interpret(eval); } catch (Throw rascalException) { eval.getEvaluator().warning(rascalException.getMessage(), rascalException.getLocation()); diff --git a/src/org/rascalmpl/semantics/dynamic/Module.java b/src/org/rascalmpl/semantics/dynamic/Module.java index 2ece2850640..75d23492e80 100644 --- a/src/org/rascalmpl/semantics/dynamic/Module.java +++ b/src/org/rascalmpl/semantics/dynamic/Module.java @@ -26,6 +26,7 @@ import org.rascalmpl.interpreter.result.Result; import org.rascalmpl.interpreter.result.ResultFactory; import org.rascalmpl.interpreter.utils.Names; +import org.rascalmpl.uri.URIUtil; import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.ISourceLocation; @@ -59,8 +60,16 @@ public Result interpret(IEvaluator> eval) { List decls = this.getBody().getToplevels(); eval.__getTypeDeclarator().evaluateDeclarations(decls, eval.getCurrentEnvt(), false); + String jobName = URIUtil.getLocationName(env.getLocation()); + eval.getMonitor().jobTodo(jobName, decls.size()); + for (Toplevel l : decls) { - l.interpret(eval); + try { + l.interpret(eval); + } + finally { + eval.getMonitor().jobStep(jobName, "toplevel", 1); + } } } catch (RuntimeException e) { diff --git a/src/org/rascalmpl/shell/ModuleRunner.java b/src/org/rascalmpl/shell/ModuleRunner.java index 63464bd6eb1..63ff6c51530 100644 --- a/src/org/rascalmpl/shell/ModuleRunner.java +++ b/src/org/rascalmpl/shell/ModuleRunner.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.io.OutputStream; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.interpreter.Evaluator; import io.usethesource.vallang.IInteger; @@ -14,8 +15,8 @@ public class ModuleRunner implements ShellRunner { private final Evaluator eval; - public ModuleRunner(InputStream input, OutputStream stdout, OutputStream stderr) { - eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr); + public ModuleRunner(InputStream input, OutputStream stdout, OutputStream stderr, IRascalMonitor monitor) { + eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr, monitor); } @Override diff --git a/src/org/rascalmpl/shell/REPLRunner.java b/src/org/rascalmpl/shell/REPLRunner.java index d218383b957..7ccb90997ba 100755 --- a/src/org/rascalmpl/shell/REPLRunner.java +++ b/src/org/rascalmpl/shell/REPLRunner.java @@ -5,11 +5,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PrintWriter; import java.net.URISyntaxException; import java.util.Map; -import org.rascalmpl.ideservices.BasicIDEServices; import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.repl.BaseREPL; @@ -33,14 +31,14 @@ private static File getHistoryFile() throws IOException { return historyFile; } - public REPLRunner(InputStream stdin, OutputStream stderr, OutputStream stdout, Terminal term) + public REPLRunner(InputStream stdin, OutputStream stderr, OutputStream stdout, Terminal term, IDEServices services) throws IOException, URISyntaxException { super(makeInterpreter(stdin, stderr, stdout, true, term.isAnsiSupported(), getHistoryFile(), term), null, - stdin, stderr, stdout, true, term.isAnsiSupported(), getHistoryFile(), term, new BasicIDEServices(new PrintWriter(stderr))); + stdin, stderr, stdout, true, term.isAnsiSupported(), getHistoryFile(), term, services); } - public REPLRunner(ILanguageProtocol language) throws IOException, URISyntaxException { - super(language, null, null, null, null, true, true, new File(""), null, new BasicIDEServices(new PrintWriter(System.err))); + public REPLRunner(ILanguageProtocol language, IDEServices services) throws IOException, URISyntaxException { + super(language, null, null, null, null, true, true, new File(""), null, services); } private static ILanguageProtocol makeInterpreter(InputStream stdin, OutputStream stderr, OutputStream stdout, @@ -50,9 +48,7 @@ private static ILanguageProtocol makeInterpreter(InputStream stdin, OutputStream new RascalInterpreterREPL(prettyPrompt, allowColors, getHistoryFile()) { @Override protected Evaluator constructEvaluator(InputStream input, OutputStream stdout, OutputStream stderr, IDEServices services) { - Evaluator eval = ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr); - eval.setMonitor(services); - return eval; + return ShellEvaluatorFactory.getDefaultEvaluator(input, stdout, stderr, services); } @Override diff --git a/src/org/rascalmpl/shell/RascalShell.java b/src/org/rascalmpl/shell/RascalShell.java index 86a9a53fe8d..f7abdeab718 100644 --- a/src/org/rascalmpl/shell/RascalShell.java +++ b/src/org/rascalmpl/shell/RascalShell.java @@ -15,9 +15,13 @@ package org.rascalmpl.shell; import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import org.rascalmpl.debug.IRascalMonitor; +import org.rascalmpl.ideservices.BasicIDEServices; +import org.rascalmpl.ideservices.IDEServices; import org.rascalmpl.interpreter.utils.RascalManifest; - import jline.Terminal; import jline.TerminalFactory; import jline.TerminalSupport; @@ -32,6 +36,7 @@ private static void printVersionNumber(){ } public static void main(String[] args) throws IOException { + System.setProperty("apple.awt.UIElement", "true"); // turns off the annoying desktop icon printVersionNumber(); try { @@ -44,7 +49,8 @@ public static void main(String[] args) throws IOException { return; } else { - runner = new ModuleRunner(System.in, System.out, System.err); + var monitor = IRascalMonitor.buildConsoleMonitor(System.in, System.out); + runner = new ModuleRunner(System.in, monitor instanceof OutputStream ? (OutputStream) monitor : System.out, System.err, monitor); } } else { @@ -60,7 +66,11 @@ public static void main(String[] args) throws IOException { } term = new EclipseTerminalConnection(term, Integer.parseInt(sneakyRepl)); } - runner = new REPLRunner(System.in, System.err, System.out, term); + + IRascalMonitor monitor = IRascalMonitor.buildConsoleMonitor(System.in, System.out); + + IDEServices services = new BasicIDEServices(new PrintWriter(System.err), monitor); + runner = new REPLRunner(System.in, System.err, monitor instanceof OutputStream ? (OutputStream) monitor : System.out, term, services); } runner.run(args); diff --git a/src/org/rascalmpl/shell/ShellEvaluatorFactory.java b/src/org/rascalmpl/shell/ShellEvaluatorFactory.java index 7507e2b8cc4..63eafa3da38 100644 --- a/src/org/rascalmpl/shell/ShellEvaluatorFactory.java +++ b/src/org/rascalmpl/shell/ShellEvaluatorFactory.java @@ -6,7 +6,7 @@ import java.io.OutputStream; import java.net.URISyntaxException; -import org.rascalmpl.interpreter.ConsoleRascalMonitor; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; @@ -27,14 +27,13 @@ public class ShellEvaluatorFactory { - public static Evaluator getDefaultEvaluator(InputStream input, OutputStream stdout, OutputStream stderr) { + public static Evaluator getDefaultEvaluator(InputStream input, OutputStream stdout, OutputStream stderr, IRascalMonitor monitor) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); - Evaluator evaluator = new Evaluator(vf, input, stderr, stdout, root, heap); + Evaluator evaluator = new Evaluator(vf, input, stderr, stdout, root, heap, monitor); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); - evaluator.setMonitor(new ConsoleRascalMonitor()); URIResolverRegistry reg = URIResolverRegistry.getInstance(); if (!reg.getRegisteredInputSchemes().contains("project") && !reg.getRegisteredLogicalSchemes().contains("project")) { @@ -47,15 +46,13 @@ public static Evaluator getDefaultEvaluator(InputStream input, OutputStream stdo return evaluator; } - public static Evaluator getDefaultEvaluatorForLocation(File fileOrFolderInProject, InputStream input, OutputStream stdout, OutputStream stderr) { + public static Evaluator getDefaultEvaluatorForLocation(File fileOrFolderInProject, InputStream input, OutputStream stdout, OutputStream stderr, IRascalMonitor monitor) { GlobalEnvironment heap = new GlobalEnvironment(); ModuleEnvironment root = heap.addModule(new ModuleEnvironment(ModuleEnvironment.SHELL_MODULE, heap)); IValueFactory vf = ValueFactoryFactory.getValueFactory(); - Evaluator evaluator = new Evaluator(vf, input, stderr, stdout, root, heap); + Evaluator evaluator = new Evaluator(vf, input, stderr, stdout, root, heap, monitor); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); - evaluator.setMonitor(new ConsoleRascalMonitor()); - ISourceLocation rootFolder = inferProjectRoot(fileOrFolderInProject); if (rootFolder != null) { configureProjectEvaluator(evaluator, rootFolder); diff --git a/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java b/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java index 36cf36910e4..72e0971e8b2 100644 --- a/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java +++ b/src/org/rascalmpl/test/infrastructure/RascalJUnitParallelRecursiveTestRunner.java @@ -52,6 +52,7 @@ * */ public class RascalJUnitParallelRecursiveTestRunner extends Runner { + private final int numberOfWorkers; private final Semaphore importsCompleted = new Semaphore(0); private final Semaphore waitForRunning = new Semaphore(0); @@ -66,7 +67,6 @@ public class RascalJUnitParallelRecursiveTestRunner extends Runner { private final ISourceLocation projectRoot; - public RascalJUnitParallelRecursiveTestRunner(Class clazz) { System.err.println("Rascal JUnit uses Rascal version " + RascalManifest.getRascalVersionNumber()); @@ -95,17 +95,12 @@ public RascalJUnitParallelRecursiveTestRunner(Class clazz) { @Override public Description getDescription() { if (rootDesc == null) { - long start = System.nanoTime(); fillModuleWorkList(); - long stop = System.nanoTime(); - reportTime("Iterating modules", start, stop); startModuleTesters(); - start = System.nanoTime(); rootDesc = Description.createSuiteDescription(projectRoot.toString()); processIncomingModuleDescriptions(rootDesc); - stop = System.nanoTime(); - reportTime("Importing modules, looking for tests", start, stop); + assert descriptions.isEmpty(); } return rootDesc; @@ -115,18 +110,10 @@ public Description getDescription() { public void run(RunNotifier notifier) { assert rootDesc != null; notifier.fireTestRunStarted(rootDesc); - long start = System.nanoTime(); runTests(notifier); - long stop = System.nanoTime(); - reportTime("Testing modules", start, stop); notifier.fireTestRunFinished(new Result()); } - private void reportTime(String job, long start, long stop) { - System.out.println(job + ": " + ((stop-start)/1000_000) + "ms"); - System.out.flush(); - } - private void processIncomingModuleDescriptions(Description rootDesc) { int completed = 0; while (completed < numberOfWorkers) { @@ -145,7 +132,6 @@ private void processIncomingModuleDescriptions(Description rootDesc) { } } - private void startModuleTesters() { for (int i = 0; i < numberOfWorkers; i++) { new ModuleTester("JUnit Rascal Evaluator " + (i + 1)).start(); @@ -183,16 +169,18 @@ private void runTests(RunNotifier notifier) { newResult.accept(notifier); } } + + TestFramework.getCommonMonitor().endAllJobs(); assert results.isEmpty(); } public class ModuleTester extends Thread { - private GlobalEnvironment heap; private ModuleEnvironment root; - private PrintWriter stderr = new PrintWriter(System.err); - private PrintWriter stdout = new PrintWriter(System.out); + + private PrintWriter stderr; + private PrintWriter stdout; private Evaluator evaluator; private final List testModules = new ArrayList<>(); @@ -210,31 +198,30 @@ public void run() { private void runTests() { try { if (waitForRunSignal()) { - for (Description mod: testModules) { - Listener trl = new Listener(mod, stderr); - - if (mod.getAnnotations().stream().anyMatch(t -> t instanceof CompilationFailed)) { - results.add(notifier -> { - notifier.fireTestStarted(mod); - notifier.fireTestFailure(new Failure(mod, new IllegalArgumentException(mod.getDisplayName() + " had import/compilation errors"))); - }); - continue; - } - - long start = System.nanoTime(); - TestEvaluator runner = new TestEvaluator(evaluator, trl); - runner.test(mod.getDisplayName()); - long stop = System.nanoTime(); - long duration = (stop - start) / 1000_000; - if (duration > 10_000) { - // longer that 10s - System.err.println("Testing module " + mod.getClassName() + " took: " + duration + "ms"); - System.err.flush(); + evaluator.job("Testing modules", testModules.size(), (String jn) -> { + for (Description mod: testModules) { + evaluator.jobStep(jn, mod.getDisplayName(), 1); + Listener trl = new Listener(mod, stdout); + + if (mod.getAnnotations().stream().anyMatch(t -> t instanceof CompilationFailed)) { + results.add(notifier -> { + notifier.fireTestStarted(mod); + notifier.fireTestFailure(new Failure(mod, new IllegalArgumentException(mod.getDisplayName() + " had import/compilation errors"))); + }); + continue; + } + + TestEvaluator runner = new TestEvaluator(evaluator, trl); + runner.test(mod.getDisplayName()); + + stdout.flush(); + stderr.flush(); } - stdout.flush(); - stderr.flush(); - } + + return true; + }); } + } finally { workersCompleted.release(); @@ -252,57 +239,70 @@ private boolean waitForRunSignal() { } private void processModules() { - String module; - try { - while ((module = modules.poll()) != null) { - try { - evaluator.doImport(new NullRascalMonitor(), module); - } - catch (Throwable e) { - synchronized(stderr) { - stderr.println("Could not import " + module + " for testing..."); - stderr.println(e.getMessage()); - e.printStackTrace(stderr); - } - - // register a failing module to make sure we report failure later on. - - Description testDesc = Description.createTestDescription(getClass(), module, new CompilationFailed() { - @Override - public Class annotationType() { - return getClass(); - } - }); + evaluator.job("Importing test modules", 0, (jn) -> { + try { + String module; + + while ((module = modules.poll()) != null) { + evaluator.jobTodo(jn, 1); + + try { + evaluator.doImport(new NullRascalMonitor(), module); + } + catch (Throwable e) { + synchronized(stdout) { + evaluator.warning("Could not import " + module + " for testing...", null); + evaluator.warning(e.getMessage(), null); + e.printStackTrace(evaluator.getOutPrinter()); + } + + // register a failing module to make sure we report failure later on. + + Description testDesc = Description.createTestDescription(getClass(), module, new CompilationFailed() { + @Override + public Class annotationType() { + return getClass(); + } + }); - testModules.add(testDesc); - descriptions.add(testDesc); + testModules.add(testDesc); + descriptions.add(testDesc); - continue; - } + continue; + } + finally { + evaluator.jobStep(jn, "Imported " + module); + } - ModuleEnvironment moduleEnv = heap.getModule(module.replaceAll("\\\\","")); - if (!moduleEnv.getTests().isEmpty()) { - Description modDesc = Description.createSuiteDescription(module); - for (AbstractFunction f : moduleEnv.getTests()) { - modDesc.addChild(Description.createTestDescription(getClass(), RascalJUnitTestRunner.computeTestName(f.getName(), f.getAst().getLocation()))); + ModuleEnvironment moduleEnv = heap.getModule(module.replaceAll("\\\\","")); + if (!moduleEnv.getTests().isEmpty()) { + Description modDesc = Description.createSuiteDescription(module); + for (AbstractFunction f : moduleEnv.getTests()) { + modDesc.addChild(Description.createTestDescription(getClass(), RascalJUnitTestRunner.computeTestName(f.getName(), f.getAst().getLocation()))); + } + descriptions.add(modDesc); + testModules.add(modDesc); } - descriptions.add(modDesc); - testModules.add(modDesc); } + // let's shuffle them + Collections.shuffle(testModules); } - // let's shuffle them - Collections.shuffle(testModules); - } - finally { - importsCompleted.release(); - } + finally { + importsCompleted.release(); + } + + return true; + }); } 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, TestFramework.getCommonMonitor()); + stdout = new PrintWriter(evaluator.getStdOut()); + stderr = new PrintWriter(evaluator.getStdErr()); - evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap); evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); evaluator.getConfiguration().setErrors(true); @@ -334,11 +334,12 @@ private Description getDescription(String name, ISourceLocation loc) { @Override public void start(String context, int count) { - // starting a module + } + @Override public void done() { - // a module was done + } @Override diff --git a/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java b/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java index 43acea0b68b..a6dbd00c481 100644 --- a/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java +++ b/src/org/rascalmpl/test/infrastructure/RascalJUnitTestRunner.java @@ -61,8 +61,8 @@ public class RascalJUnitTestRunner extends Runner { try { heap = new GlobalEnvironment(); root = heap.addModule(new ModuleEnvironment("___junit_test___", heap)); - - evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap); + evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap, TestFramework.getCommonMonitor()); + evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); evaluator.getConfiguration().setErrors(true); } @@ -165,7 +165,7 @@ public static List getRecursiveModuleList(ISourceLocation root, List getRecursiveModuleList(ISourceLocation root, List modules = new ArrayList<>(10); - for (String src : new RascalManifest().getSourceRoots(projectRoot)) { - getRecursiveModuleList(URIUtil.getChildLocation(projectRoot, src + "/" + prefix.replaceAll("::", "/")), modules); - } - - Collections.shuffle(modules); // make sure the import order is different, not just the reported modules - - for (String module : modules) { - String name = prefix + "::" + module; - Description modDesc = Description.createSuiteDescription(name); - - try { - System.err.println("Loading module:" + name); - evaluator.doImport(new NullRascalMonitor(), name); - List tests = heap.getModule(name.replaceAll("\\\\","")).getTests(); + evaluator.job("Loading test modules", 1, (jobName) -> { + try { + evaluator.jobTodo(jobName, 1); + List modules = new ArrayList<>(10); + for (String src : new RascalManifest().getSourceRoots(projectRoot)) { + getRecursiveModuleList(URIUtil.getChildLocation(projectRoot, src + "/" + prefix.replaceAll("::", "/")), modules); + } - if (tests.isEmpty()) { - System.err.println("\tskipping. Module has no tests."); - continue; - } + Collections.shuffle(modules); // make sure the import order is different, not just the reported modules + evaluator.jobStep(jobName, "detected " + modules.size() + " modules"); + evaluator.jobTodo(jobName, modules.size()); + for (String module : modules) { + String name = prefix + "::" + module; + Description modDesc = Description.createSuiteDescription(name); + evaluator.jobStep(jobName, "Preparing " + name); + + try { + evaluator.doImport(new NullRascalMonitor(), name); + List tests = heap.getModule(name.replaceAll("\\\\","")).getTests(); - System.err.println("\t adding " + tests.size() + " tests for " + name); - desc.addChild(modDesc); + if (tests.isEmpty()) { + continue; + } + + desc.addChild(modDesc); - // the order of the tests aren't decided by this list so no need to randomly order them. - for (AbstractFunction f : tests) { - modDesc.addChild(Description.createTestDescription(clazz, computeTestName(f.getName(), f.getAst().getLocation()))); + // the order of the tests aren't decided by this list so no need to randomly order them. + for (AbstractFunction f : tests) { + modDesc.addChild(Description.createTestDescription(clazz, computeTestName(f.getName(), f.getAst().getLocation()))); + } } - } - catch (Throwable e) { - - desc.addChild(modDesc); + catch (Throwable e) { + desc.addChild(modDesc); - Description testDesc = Description.createTestDescription(clazz, name + " compilation failed", new CompilationFailed() { - @Override - public Class annotationType() { - return getClass(); - } - }); + Description testDesc = Description.createTestDescription(clazz, name + " compilation failed", new CompilationFailed() { + @Override + public Class annotationType() { + return getClass(); + } + }); - modDesc.addChild(testDesc); + modDesc.addChild(testDesc); + } } - } - return desc; - } catch (IOException e) { - Description testDesc = Description.createTestDescription(clazz, prefix + " compilation failed: " + e.getMessage(), new CompilationFailed() { - @Override - public Class annotationType() { - return getClass(); - } - }); + return true; + } catch (IOException e) { + Description testDesc = Description.createTestDescription(clazz, prefix + " compilation failed: " + e.getMessage(), new CompilationFailed() { + @Override + public Class annotationType() { + return getClass(); + } + }); - desc.addChild(testDesc); + desc.addChild(testDesc); - System.err.println("[ERROR] Could not create tests suite: " + e); - - return desc; - } + evaluator.warning("Could not create tests suite: " + e, URIUtil.rootLocation("unknown")); + + return false; + } + }); + + return desc; } @Override diff --git a/src/org/rascalmpl/test/infrastructure/TestFramework.java b/src/org/rascalmpl/test/infrastructure/TestFramework.java index 1dfe3674dad..3a8f6cd0f73 100644 --- a/src/org/rascalmpl/test/infrastructure/TestFramework.java +++ b/src/org/rascalmpl/test/infrastructure/TestFramework.java @@ -28,6 +28,7 @@ import java.util.Set; import org.junit.After; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; @@ -45,6 +46,14 @@ public class TestFramework { + private static class InstanceHolder { + final static IRascalMonitor monitor = IRascalMonitor.buildConsoleMonitor(System.in, System.out); + } + + public static IRascalMonitor getCommonMonitor() { + return InstanceHolder.monitor; + } + private final static Evaluator evaluator; private final static GlobalEnvironment heap; private final static ModuleEnvironment root; @@ -56,10 +65,11 @@ public class TestFramework { heap = new GlobalEnvironment(); root = heap.addModule(new ModuleEnvironment("___test___", heap)); - stderr = new PrintWriter(System.err); - stdout = new PrintWriter(System.out); - evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap); - + evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, getCommonMonitor(), root, heap); + + stdout = evaluator.getOutPrinter(); + stderr = evaluator.getErrorPrinter(); + evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); RascalJUnitTestRunner.configureProjectEvaluator(evaluator, RascalJUnitTestRunner.inferProjectRoot(TestFramework.class)); @@ -89,9 +99,7 @@ private void reset() { catch (IOException e) { } } - generatedModules.clear(); - - + generatedModules.clear(); evaluator.getAccumulators().clear(); } @@ -133,7 +141,6 @@ public TestFramework prepare(String command) { try { reset(); execute(command); - } catch (StaticError e) { throw e; diff --git a/src/org/rascalmpl/values/RascalFunctionValueFactory.java b/src/org/rascalmpl/values/RascalFunctionValueFactory.java index 9e496b66c46..268d049479f 100644 --- a/src/org/rascalmpl/values/RascalFunctionValueFactory.java +++ b/src/org/rascalmpl/values/RascalFunctionValueFactory.java @@ -105,7 +105,7 @@ private ParserGenerator getParserGenerator() { private Class> generateParser(IMap grammar) { try { - return getParserGenerator().getNewParser(new NullRascalMonitor(), URIUtil.rootLocation("parser-generator"), "$GENERATED_PARSER$" + Math.abs(grammar.hashCode()), grammar); + return getParserGenerator().getNewParser(ctx.getEvaluator().getMonitor(), URIUtil.rootLocation("parser-generator"), "$GENERATED_PARSER$" + Math.abs(grammar.hashCode()), grammar); } catch (ExceptionInInitializerError e) { throw new ImplementationError(e.getMessage(), e); @@ -118,7 +118,7 @@ protected Class> getParserClass(IMap protected void writeParserClass(IMap grammar, ISourceLocation target) throws IOException { getParserGenerator().writeNewParser( - new NullRascalMonitor(), + ctx.getEvaluator().getMonitor(), URIUtil.rootLocation("parser-generator"), "$GENERATED_PARSER$" + Math.abs(grammar.hashCode()), grammar, diff --git a/test/org/rascalmpl/MatchFingerprintTest.java b/test/org/rascalmpl/MatchFingerprintTest.java index 41614b94b05..8da270dcca8 100644 --- a/test/org/rascalmpl/MatchFingerprintTest.java +++ b/test/org/rascalmpl/MatchFingerprintTest.java @@ -20,6 +20,8 @@ import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; +import org.rascalmpl.test.infrastructure.TestFramework; + import io.usethesource.vallang.IConstructor; import io.usethesource.vallang.IInteger; import io.usethesource.vallang.ISourceLocation; @@ -46,7 +48,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); + private final Evaluator eval = new Evaluator(IRascalValueFactory.getInstance(), System.in, System.err, System.out, root, heap, TestFramework.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 89c01a6701c..15134bb00bb 100644 --- a/test/org/rascalmpl/test/functionality/ParallelEvaluatorsTests.java +++ b/test/org/rascalmpl/test/functionality/ParallelEvaluatorsTests.java @@ -11,8 +11,10 @@ import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; +import org.rascalmpl.debug.IRascalMonitor; import org.rascalmpl.interpreter.Evaluator; import org.rascalmpl.interpreter.ITestResultListener; +import org.rascalmpl.interpreter.NullRascalMonitor; import org.rascalmpl.interpreter.env.GlobalEnvironment; import org.rascalmpl.interpreter.env.ModuleEnvironment; import org.rascalmpl.interpreter.load.StandardLibraryContributor; @@ -21,7 +23,8 @@ import io.usethesource.vallang.ISourceLocation; public class ParallelEvaluatorsTests { - + private static final IRascalMonitor monitor = new NullRascalMonitor(); + private static final String[] testModules = new String[] { "lang::rascal::tests::library::ValueIO", "lang::rascal::tests::library::Map", @@ -32,27 +35,35 @@ 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); + var evaluator = new Evaluator(ValueFactoryFactory.getValueFactory(), System.in, System.err, System.out, root, heap, monitor); + evaluator.addRascalSearchPathContributor(StandardLibraryContributor.getInstance()); evaluator.setTestResultListener(new ITestResultListener() { @Override - public void start(String context, int count) { } + public void start(String context, int count) { + + } @Override public void report(boolean successful, String test, ISourceLocation loc, String message, Throwable exception) { - if (exception != null) { - System.err.println("Got exception: " + exception); - exception.printStackTrace(System.err); - throw new RuntimeException(exception); - } + + if (exception != null) { + evaluator.warning("Got exception: " + exception, loc); + exception.printStackTrace(evaluator.getOutPrinter()); + throw new RuntimeException(exception); + } } @Override - public void ignored(String test, ISourceLocation loc) { } + public void ignored(String test, ISourceLocation loc) { + + } @Override - public void done() {} + public void done() { + + } }); return evaluator; } @@ -76,9 +87,9 @@ public void testMultipleEvaluators() { return; } var currentTarget = currentModule.get(); - System.out.println("Testing module: " + currentTarget + " started"); - evaluator.doImport(null, currentTarget); - if (!evaluator.runTests(null)) { + + evaluator.doImport(monitor, currentTarget); + if (!evaluator.runTests(monitor)) { result.set(false); } allDone.await(); @@ -96,7 +107,7 @@ public void testMultipleEvaluators() { } } - }); + }, "Evaluator parallel stress test " + i); runner.setDaemon(true); runner.start(); } @@ -120,10 +131,8 @@ public void testMultipleEvaluators() { } finally { close.set(true); + monitor.endAllJobs(); } - - - } }