diff --git a/pom.xml b/pom.xml index f3b9ce930b5..eab0ab9e17f 100644 --- a/pom.xml +++ b/pom.xml @@ -125,7 +125,7 @@ rascal-maven-plugin ${rascal-maven.version} - false + true ${project.build.outputDirectory} ${project.basedir}/src/org/rascalmpl/library diff --git a/src/org/rascalmpl/interpreter/utils/JavaBridge.java b/src/org/rascalmpl/interpreter/utils/JavaBridge.java index 0569e92c43b..fc0c079a853 100644 --- a/src/org/rascalmpl/interpreter/utils/JavaBridge.java +++ b/src/org/rascalmpl/interpreter/utils/JavaBridge.java @@ -15,6 +15,7 @@ *******************************************************************************/ package org.rascalmpl.interpreter.utils; +import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; @@ -30,6 +31,7 @@ import javax.tools.JavaFileObject; import javax.tools.ToolProvider; +import org.checkerframework.checker.units.qual.t; import org.rascalmpl.ast.Expression; import org.rascalmpl.ast.FunctionDeclaration; import org.rascalmpl.ast.KeywordFormal; @@ -107,6 +109,10 @@ public JavaBridge(List classLoaders, IValueFactory valueFactory, Co public Class compileJava(ISourceLocation loc, String className, String source) { return compileJava(loc, className, getClass(), source); } + + public void compileJava(ISourceLocation loc, String className, String source, OutputStream classBytes) { + compileJava(loc, className, getClass(), source, classBytes); + } public Class compileJava(ISourceLocation loc, String className, Class parent, String source) { try { @@ -130,6 +136,33 @@ public Class compileJava(ISourceLocation loc, String className, Class } } + public Class loadClass(InputStream in) throws IOException, ClassNotFoundException { + List commandline = Arrays.asList(new String[] {"-proc:none", "-cp", config.getRascalJavaClassPathProperty()}); + JavaCompiler javaCompiler = new JavaCompiler(getClass().getClassLoader(), null, commandline); + return javaCompiler.load(in); + } + + public void compileJava(ISourceLocation loc, String className, Class parent, String source, OutputStream classBytes) { + try { + // watch out, if you start sharing this compiler, classes will not be able to reload + List commandline = Arrays.asList(new String[] {"-proc:none", "-cp", config.getRascalJavaClassPathProperty()}); + JavaCompiler javaCompiler = new JavaCompiler(parent.getClassLoader(), null, commandline); + javaCompiler.compile(classBytes, className, source, null); + } + catch (ClassCastException e) { + throw new JavaCompilation(e.getMessage(), e); + } + catch (JavaCompilerException e) { + if (!e.getDiagnostics().getDiagnostics().isEmpty()) { + Diagnostic msg = e.getDiagnostics().getDiagnostics().iterator().next(); + throw new JavaCompilation(msg.getMessage(null) + " at " + msg.getLineNumber() + ", " + msg.getColumnNumber() + " with classpath [" + config.getRascalJavaClassPathProperty() + "]", e); + } + else { + throw new JavaCompilation(e.getMessage(), e); + } + } + } + private String getClassName(FunctionDeclaration declaration) { Tags tags = declaration.getTags(); diff --git a/src/org/rascalmpl/interpreter/utils/JavaCompiler.java b/src/org/rascalmpl/interpreter/utils/JavaCompiler.java index 56dea130839..d7dbfc1d0cf 100644 --- a/src/org/rascalmpl/interpreter/utils/JavaCompiler.java +++ b/src/org/rascalmpl/interpreter/utils/JavaCompiler.java @@ -16,6 +16,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -28,6 +29,11 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; import java.util.Set; import javax.tools.DiagnosticCollector; @@ -41,7 +47,9 @@ import javax.tools.StandardLocation; import javax.tools.ToolProvider; +import org.rascalmpl.library.Prelude; import org.rascalmpl.uri.URIUtil; +import org.rascalmpl.uri.jar.JarInputStreamFileTree; /** * Compile a String or other {@link CharSequence}, returning a Java @@ -162,10 +170,13 @@ public synchronized Class compile(final String qualifiedClassName, final DiagnosticCollector diagnosticsList, final Class... types) throws JavaCompilerException, ClassCastException { - if (diagnosticsList != null) + if (diagnosticsList != null) { diagnostics = diagnosticsList; - else + } + else { diagnostics = new DiagnosticCollector(); + } + Map classes = new HashMap(1); classes.put(qualifiedClassName, javaSource); Map> compiled = compile(classes, diagnostics); @@ -245,6 +256,28 @@ public synchronized Map> compile( } } + public void compile(OutputStream classBytes, String qualifiedClassName, CharSequence classSource, final DiagnosticCollector diagnostics) throws JavaCompilerException { + Map fileMap = new HashMap<>(); + + try { + fileMap.put(qualifiedClassName, classSource); + + // ignoring return class here + compile(fileMap, diagnostics); + + // side-effect alert: + // now the local classloader contains the .class file + classLoader.outputClassesToJar(qualifiedClassName, classBytes); + } + catch (IOException e) { + throw new JavaCompilerException(fileMap.keySet(), e, diagnostics); + } + } + + public Class load(InputStream file) throws IOException, ClassNotFoundException { + return classLoader.inputClassesFromJar(file); + } + /** * Load a class that was generated by this instance or accessible from its * parent class loader. Use this method if you need access to additional @@ -310,6 +343,8 @@ public ClassLoader getClassLoader() { public JavaFileManager getFileManager() { return javaFileManager; } + + } /** @@ -561,6 +596,51 @@ final class ClassLoaderImpl extends ClassLoader { super(parentClassLoader); } + public void outputClassesToJar(String qualifiedClassName, OutputStream output) throws IOException { + Manifest manifest = new Manifest(); + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); + manifest.getMainAttributes().put(Attributes.Name.MAIN_CLASS, qualifiedClassName); + + try (JarOutputStream target = new JarOutputStream(output, manifest)) { + for (Entry entry : classes.entrySet()) { + String className = entry.getKey(); + JavaFileObjectImpl file = (JavaFileObjectImpl) entry.getValue(); + JarEntry jarEntry = new JarEntry(className); + jarEntry.setTime(file.getLastModified()); + target.putNextEntry(jarEntry); + target.write(file.getByteCode()); + target.closeEntry(); + } + } + } + + public Class inputClassesFromJar(InputStream in) throws IOException, ClassNotFoundException { + try (JarInputStream jarIn = new JarInputStream(in)) { + Manifest mf = jarIn.getManifest(); + String mainClass = (String) mf.getMainAttributes().get(Attributes.Name.MAIN_CLASS); + JarEntry jarEntry; + + if (mainClass == null) { + throw new IOException("missing Main-Class in jar manifest"); + } + + while ((jarEntry = jarIn.getNextJarEntry()) != null) { + if (!jarEntry.isDirectory()) { + var className = jarEntry.getName(); + var file = new JavaFileObjectImpl(className, JavaFileObject.Kind.CLASS); + + try (var fo = file.openOutputStream()) { + fo.write(Prelude.consumeInputStream(jarIn)); + } + + add(className, file); + } + } + + return loadClass(mainClass); + } + } + /** * @return An collection of JavaFileObject instances for the classes in the * class loader. diff --git a/src/org/rascalmpl/library/ParseTree.rsc b/src/org/rascalmpl/library/ParseTree.rsc index 482dc4a3825..a517e1210a2 100644 --- a/src/org/rascalmpl/library/ParseTree.rsc +++ b/src/org/rascalmpl/library/ParseTree.rsc @@ -310,7 +310,7 @@ data Symbol data Symbol // <19> = \conditional(Symbol symbol, set[Condition] conditions); -public bool subtype(Symbol::\sort(_), Symbol::\adt("Tree", _)) = true; +bool subtype(Symbol::\sort(_), Symbol::\adt("Tree", _)) = true; @doc{ #### Synopsis @@ -341,7 +341,7 @@ data Condition Nested priority is flattened. } -public Production priority(Symbol s, [*Production a, priority(Symbol _, list[Production] b), *Production c]) +Production priority(Symbol s, [*Production a, priority(Symbol _, list[Production] b), *Production c]) = priority(s,a+b+c); @doc{ @@ -446,13 +446,13 @@ catch ParseError(loc l): { } ``` } -public &T<:Tree parse(type[&T<:Tree] begin, str input, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) +&T<:Tree parse(type[&T<:Tree] begin, str input, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) = parser(begin, allowAmbiguity=allowAmbiguity, hasSideEffects=hasSideEffects, filters=filters)(input, |unknown:///|); -public &T<:Tree parse(type[&T<:Tree] begin, str input, loc origin, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) +&T<:Tree parse(type[&T<:Tree] begin, str input, loc origin, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) = parser(begin, allowAmbiguity=allowAmbiguity, hasSideEffects=hasSideEffects, filters=filters)(input, origin); -public &T<:Tree parse(type[&T<:Tree] begin, loc input, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) +&T<:Tree parse(type[&T<:Tree] begin, loc input, bool allowAmbiguity=false, bool hasSideEffects=false, set[Tree(Tree)] filters={}) = parser(begin, allowAmbiguity=allowAmbiguity, hasSideEffects=hasSideEffects, filters=filters)(input, input); @doc{ @@ -485,7 +485,7 @@ The parse function behaves differently depending of the given keyword parameters * parse forest to be constructed in polynomial time. } @javaClass{org.rascalmpl.library.Prelude} -public java &T (value input, loc origin) parser(type[&T] grammar, bool allowAmbiguity=false, bool hasSideEffects=false, bool firstAmbiguity=false, set[Tree(Tree)] filters={}); +java &T (value input, loc origin) parser(type[&T] grammar, bool allowAmbiguity=false, bool hasSideEffects=false, bool firstAmbiguity=false, set[Tree(Tree)] filters={}); @doc{ #### Synopsis @@ -498,7 +498,7 @@ This parser generator behaves the same as the `parser` function, but it produces nonterminal parameter. This can be used to select a specific non-terminal from the grammar to use as start-symbol for parsing. } @javaClass{org.rascalmpl.library.Prelude} -public java &U (type[&U] nonterminal, value input, loc origin) parsers(type[&T] grammar, bool allowAmbiguity=false, bool hasSideEffects=false, bool firstAmbiguity=false, set[Tree(Tree)] filters={}); +java &U (type[&U] nonterminal, value input, loc origin) parsers(type[&T] grammar, bool allowAmbiguity=false, bool hasSideEffects=false, bool firstAmbiguity=false, set[Tree(Tree)] filters={}); @synopsis{Parse the input but instead of returning the entire tree, return the trees for the first ambiguous substring.} @description{ @@ -508,12 +508,75 @@ the cost of constructing nested ambiguity clusters. If the input sentence is not ambiguous after all, simply the entire tree is returned. } -public Tree firstAmbiguity(type[Tree] begin, str input) +Tree firstAmbiguity(type[Tree] begin, str input) = parser(begin, firstAmbiguity=true)(input, |unknown:///|); -public Tree firstAmbiguity(type[Tree] begin, loc input) +Tree firstAmbiguity(type[Tree] begin, loc input) = parser(begin, firstAmbiguity=true)(input, input); +@javaClass{org.rascalmpl.library.Prelude} +@synopsis{Generate a parser and store it in serialized form for later reuse.} +@description{ +The stored parsers would be able to be recovered later using ((loadParsers)). + +For any concrete grammar, a.k.a. reified syntax type, `g` it holds that: +* after `storeParsers(g, file);` +* then `g = loadParsers(file);` +* and given `h = parsers(g);` +* then for all valid `nt`, `input` and `origin`: `g(nt, input, origin) == h(nt, input, origin)` + +In other words, a loaded parser function behaves exactly as a freshly generated parser +for the same grammar, if (and only if) it was stored for the same grammar value. +} +@benefits{ +* storing parsers is to cache the work of reifing a grammar, and generating a parser from that grammar. +* stored parsers are nice for deployment scenerios where the language is fixed and efficiency is appreciated. +} +@pitfalls{ +* caching parsers with `storeParsers` is your responsibility; the cache is not cleaned automatically when the grammar changes. +} +java void storeParsers(type[Tree] grammar, loc saveLocation); + +@javaClass{org.rascalmpl.library.Prelude} +@synopsis{Load a previously serialized parser from disk for usage} +@description{ +For any concrete grammar, a.k.a. reified syntax type, `g` it holds that: +* after `storeParsers(g, file);` +* then `g = loadParsers(file);` +* and given `h = parsers(g);` +* then for all valid `nt`, `input` and `origin`: `g(nt, input, origin) == h(nt, input, origin)` + +In other words, a loaded parser function behaves exactly as a freshly generated parser +for the same grammar, if (and only if) it was stored for the same grammar value. +} +@examples{ + +First we store a parser: +```rascal-shell +import ParseTree; +syntax E = E "+" E | "e"; +storeParsers(#E, |test-temp:///E.parsers|) +``` + +Here we show a new shell does not even know about the grammar: +```rascal-shell,errors +#E +``` + +Then in a next run, we load the parser and use it: +``` +import ParseTree; +p = loadParsers(|test-temp:///E.parsers|); +p(type(sort("E"), ()), "e+e", |src:///|); +} +@benefits{ +* loaded parsers can be used immediately without the need of loadig and executing a parser generator. +} +@pitfalls{ +* reifiying types (use of `#`) will trigger the loading of a parser generator anyway. You have to use +this notation for types to avoid that: `type(\start(sort("MySort")), ())` to avoid the computation for `#start[A]` +} +java &U (type[&U] nonterminal, value input, loc origin) loadParsers(loc savedParsers, bool allowAmbiguity=false, bool hasSideEffects=false, bool firstAmbiguity=false, set[Tree(Tree)] filters={}); @doc{ #### Synopsis @@ -540,10 +603,10 @@ First parse an expression, this results in a parse tree. Then unparse this parse unparse(parse(#Exp, "2+3")); ``` } -public str unparse(Tree tree) = ""; +str unparse(Tree tree) = ""; @javaClass{org.rascalmpl.library.Prelude} -public java str printSymbol(Symbol sym, bool withLayout); +java str printSymbol(Symbol sym, bool withLayout); @javaClass{org.rascalmpl.library.Prelude} @doc{ @@ -682,21 +745,21 @@ Can be imploded into: data Exp = add(Exp, Exp); ``` } -public java &T<:value implode(type[&T<:value] t, Tree tree); +java &T<:value implode(type[&T<:value] t, Tree tree); @doc{ #### Synopsis Annotate a parse tree node with an (error) message. } -public anno Message Tree@message; +anno Message Tree@message; @doc{ #### Synopsis Annotate a parse tree node with a list of (error) messages. } -public anno set[Message] Tree@messages; +anno set[Message] Tree@messages; @doc{ #### Synopsis @@ -742,7 +805,7 @@ anno rel[loc,loc] Tree@hyperlinks; Tree search result type for ((treeAt)). } -public data TreeSearchResult[&T<:Tree] = treeFound(&T tree) | treeNotFound(); +data TreeSearchResult[&T<:Tree] = treeFound(&T tree) | treeNotFound(); @doc{ @@ -753,7 +816,7 @@ Select the innermost Tree of a given type which is enclosed by a given location. #### Description } -public TreeSearchResult[&T<:Tree] treeAt(type[&T<:Tree] t, loc l, Tree a:appl(_, _)) { +TreeSearchResult[&T<:Tree] treeAt(type[&T<:Tree] t, loc l, Tree a:appl(_, _)) { if ((a@\loc)?, al := a@\loc, al.offset <= l.offset, al.offset + al.length >= l.offset + l.length) { for (arg <- a.args, TreeSearchResult[&T<:Tree] r:treeFound(&T<:Tree _) := treeAt(t, l, arg)) { return r; @@ -766,25 +829,25 @@ public TreeSearchResult[&T<:Tree] treeAt(type[&T<:Tree] t, loc l, Tree a:appl(_, return treeNotFound(); } -public default TreeSearchResult[&T<:Tree] treeAt(type[&T<:Tree] t, loc l, Tree root) = treeNotFound(); +default TreeSearchResult[&T<:Tree] treeAt(type[&T<:Tree] t, loc l, Tree root) = treeNotFound(); -public bool sameType(label(_,Symbol s),Symbol t) = sameType(s,t); -public bool sameType(Symbol s,label(_,Symbol t)) = sameType(s,t); -public bool sameType(Symbol s,conditional(Symbol t,_)) = sameType(s,t); -public bool sameType(conditional(Symbol s,_), Symbol t) = sameType(s,t); -public bool sameType(Symbol s, s) = true; -public default bool sameType(Symbol s, Symbol t) = false; +bool sameType(label(_,Symbol s),Symbol t) = sameType(s,t); +bool sameType(Symbol s,label(_,Symbol t)) = sameType(s,t); +bool sameType(Symbol s,conditional(Symbol t,_)) = sameType(s,t); +bool sameType(conditional(Symbol s,_), Symbol t) = sameType(s,t); +bool sameType(Symbol s, s) = true; +default bool sameType(Symbol s, Symbol t) = false; @doc{ #### Synopsis Determine if the given type is a non-terminal type. } -public bool isNonTerminalType(Symbol::\sort(str _)) = true; -public bool isNonTerminalType(Symbol::\lex(str _)) = true; -public bool isNonTerminalType(Symbol::\layouts(str _)) = true; -public bool isNonTerminalType(Symbol::\keywords(str _)) = true; -public bool isNonTerminalType(Symbol::\parameterized-sort(str _, list[Symbol] _)) = true; -public bool isNonTerminalType(Symbol::\parameterized-lex(str _, list[Symbol] _)) = true; -public bool isNonTerminalType(Symbol::\start(Symbol s)) = isNonTerminalType(s); -public default bool isNonTerminalType(Symbol s) = false; +bool isNonTerminalType(Symbol::\sort(str _)) = true; +bool isNonTerminalType(Symbol::\lex(str _)) = true; +bool isNonTerminalType(Symbol::\layouts(str _)) = true; +bool isNonTerminalType(Symbol::\keywords(str _)) = true; +bool isNonTerminalType(Symbol::\parameterized-sort(str _, list[Symbol] _)) = true; +bool isNonTerminalType(Symbol::\parameterized-lex(str _, list[Symbol] _)) = true; +bool isNonTerminalType(Symbol::\start(Symbol s)) = isNonTerminalType(s); +default bool isNonTerminalType(Symbol s) = false; diff --git a/src/org/rascalmpl/library/Prelude.java b/src/org/rascalmpl/library/Prelude.java index 177ffedb1ac..d9c3a787353 100644 --- a/src/org/rascalmpl/library/Prelude.java +++ b/src/org/rascalmpl/library/Prelude.java @@ -1149,6 +1149,16 @@ public static String consumeInputStream(Reader in) throws IOException { } return res.toString(); } + + public static byte[] consumeInputStream(InputStream in) throws IOException { + ByteArrayOutputStream res = new ByteArrayOutputStream(); + byte[] chunk = new byte[FILE_BUFFER_SIZE]; + int read; + while ((read = in.read(chunk, 0, chunk.length)) != -1) { + res.write(chunk, 0, read); + } + return res.toByteArray(); + } public IValue md5HashFile(ISourceLocation sloc){ try { @@ -2326,6 +2336,24 @@ public IFunction parser(IValue start, IBool allowAmbiguity, IBool hasSideEffect public IFunction parsers(IValue start, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { return rascalValues.parsers(start, allowAmbiguity, hasSideEffects, firstAmbiguity, filters); } + + public void storeParsers(IValue start, ISourceLocation saveLocation) { + try { + rascalValues.storeParsers(start, saveLocation); + } + catch (IOException e) { + throw RuntimeExceptionFactory.io(e.getMessage()); + } + } + + public IFunction loadParsers(ISourceLocation savedLocation, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { + try { + return rascalValues.loadParsers(savedLocation, allowAmbiguity, hasSideEffects, firstAmbiguity, filters); + } + catch (IOException | ClassNotFoundException e) { + throw RuntimeExceptionFactory.io(e.getMessage()); + } + } // REFLECT -- copy in {@link PreludeCompiled} protected IConstructor makeConstructor(TypeStore store, Type returnType, String name, IValue ...args) { diff --git a/src/org/rascalmpl/library/Relation.rsc b/src/org/rascalmpl/library/Relation.rsc index d971b5f0008..4b513f10650 100644 --- a/src/org/rascalmpl/library/Relation.rsc +++ b/src/org/rascalmpl/library/Relation.rsc @@ -36,7 +36,7 @@ public set[&T] carrier (rel[&T,&T] R) return R<0> + R<1>; } -public set[&T] carrier (rel[&T,&T,&T] R) +public set[&T] carrier (rel[&T, &T, &T] R) { return (R<0> + R<1>) + R<2>; } diff --git a/src/org/rascalmpl/library/lang/rascal/tests/concrete/Parsing.rsc b/src/org/rascalmpl/library/lang/rascal/tests/concrete/Parsing.rsc index 26b8db77af1..24fec10936d 100644 --- a/src/org/rascalmpl/library/lang/rascal/tests/concrete/Parsing.rsc +++ b/src/org/rascalmpl/library/lang/rascal/tests/concrete/Parsing.rsc @@ -62,3 +62,9 @@ test bool parsingWithAManualGrammar() && Tree t := parse(gr, "hello") && "" == "hello"; +test bool saveAndRestoreParser() { + storeParsers(#start[A], |test-temp:///parsers.jar|); + p = loadParsers(|test-temp:///parsers.jar|); + + return p(type(\start(sort("A")), ()), "a", |origin:///|) == parse(#start[A], "a", |origin:///|); +} \ No newline at end of file diff --git a/src/org/rascalmpl/parser/ParserGenerator.java b/src/org/rascalmpl/parser/ParserGenerator.java index 237404ffd56..d174bca78f5 100644 --- a/src/org/rascalmpl/parser/ParserGenerator.java +++ b/src/org/rascalmpl/parser/ParserGenerator.java @@ -30,6 +30,7 @@ import org.rascalmpl.interpreter.utils.JavaBridge; import org.rascalmpl.interpreter.utils.Profiler; import org.rascalmpl.parser.gtd.IGTD; +import org.rascalmpl.uri.URIResolverRegistry; import org.rascalmpl.values.IRascalValueFactory; import org.rascalmpl.values.ValueFactoryFactory; import org.rascalmpl.values.parsetrees.ITree; @@ -251,6 +252,42 @@ public Class> getNewParser(IRascalMon } } + /** + * Generate a parser from a Rascal grammar and write it to disk + * + * @param monitor a progress monitor; this method will contribute 100 work units + * @param loc a location for error reporting + * @param name the name of the parser for use in code generation and for later reference + * @param grammar a grammar + * @return A parser class, ready for instantiation + * @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); + + 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); + + 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); + } +} + public IString createHole(IConstructor part, IInteger size) { synchronized (evaluator) { return (IString) evaluator.call("createHole", part, size); diff --git a/src/org/rascalmpl/test/infrastructure/QuickCheck.java b/src/org/rascalmpl/test/infrastructure/QuickCheck.java index 48bc5ba23c1..25ba7105bd1 100644 --- a/src/org/rascalmpl/test/infrastructure/QuickCheck.java +++ b/src/org/rascalmpl/test/infrastructure/QuickCheck.java @@ -225,6 +225,4 @@ public ExceptionNotThrownResult(String functionName, Type[] actualTypes, super(functionName, "test did not throw '" + expected + "' exception", actualTypes, tpbindings, values); } } - - } diff --git a/src/org/rascalmpl/values/IRascalValueFactory.java b/src/org/rascalmpl/values/IRascalValueFactory.java index 405924792bf..8ebd79ffc77 100644 --- a/src/org/rascalmpl/values/IRascalValueFactory.java +++ b/src/org/rascalmpl/values/IRascalValueFactory.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.rascalmpl.values; +import java.io.IOException; import java.util.Map; import java.util.function.BiFunction; @@ -22,6 +23,7 @@ import io.usethesource.vallang.IList; import io.usethesource.vallang.IMap; import io.usethesource.vallang.ISet; +import io.usethesource.vallang.ISourceLocation; import io.usethesource.vallang.IValue; import io.usethesource.vallang.IValueFactory; import io.usethesource.vallang.type.Type; @@ -105,6 +107,24 @@ default IFunction parser(IValue reifiedGrammar, IBool allowAmbiguity, IBool hasS default IFunction parsers(IValue reifiedGrammar, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { throw new UnsupportedOperationException("This Rascal value factory does not support a parser generator:" + getClass()); } - - + + /** + * Same as `parsers` but saves the result to disk rather than wrapping it as an IFunction. + * + * @param start + * @param saveLocation + * @throws IOException + */ + default void storeParsers(IValue start, ISourceLocation saveLocation) throws IOException { + throw new UnsupportedOperationException("This Rascal value factory does not support a parser generator that can store parsers on disk." + getClass()); + } + + /** + * Reverse of storeParsers and with the same effect as the {@see parsers} method. + * @throws IOException + * @throws ClassNotFoundException + */ + default IFunction loadParsers(ISourceLocation saveLocation, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) throws IOException, ClassNotFoundException { + throw new UnsupportedOperationException("This Rascal value factory does not support a parser generator that can restore parsers from disk." + getClass()); + } } diff --git a/src/org/rascalmpl/values/RascalFunctionValueFactory.java b/src/org/rascalmpl/values/RascalFunctionValueFactory.java index de27887d9f6..958cd4fa918 100644 --- a/src/org/rascalmpl/values/RascalFunctionValueFactory.java +++ b/src/org/rascalmpl/values/RascalFunctionValueFactory.java @@ -114,6 +114,16 @@ private Class> generateParser(IMap gr protected Class> getParserClass(IMap grammar) { return parserCache.get(grammar); } + + protected void writeParserClass(IMap grammar, ISourceLocation target) throws IOException { + getParserGenerator().writeNewParser( + new NullRascalMonitor(), + URIUtil.rootLocation("parser-generator"), + "$GENERATED_PARSER$" + Math.abs(grammar.hashCode()), + grammar, + target + ); + } @Override public IFunction function(io.usethesource.vallang.type.Type functionType, BiFunction, IValue> func) { @@ -217,11 +227,34 @@ public IFunction parser(IValue reifiedGrammar, IBool allowAmbiguity, IBool hasSi AbstractAST current = ctx.getCurrentAST(); ISourceLocation caller = current != null ? current.getLocation() : URIUtil.rootLocation("unknown"); - String name = getParserGenerator().getParserMethodName(startSort); + String name = getParserMethodName(startSort); + if (name == null) { + name = generator.getParserMethodName(startSort); + } return function(functionType, new ParseFunction(ctx.getValueFactory(), caller, parser, name, allowAmbiguity, hasSideEffects, firstAmbiguity, filters)); } + protected static String getParserMethodName(IConstructor symbol) { + // we use a fast non-synchronized path for simple cases; + // this is to prevent locking the evaluator in IDE contexts + // where many calls into the evaluator/parser are fired in rapid + // succession. + + switch (symbol.getName()) { + case "start": + return "start__" + getParserMethodName(SymbolAdapter.getStart(symbol)); + case "layouts": + return "layouts_" + SymbolAdapter.getName(symbol); + case "sort": + case "lex": + case "keywords": + return SymbolAdapter.getName(symbol); + } + + return null; + } + @Override public IFunction parsers(IValue reifiedGrammar, IBool allowAmbiguity, IBool hasSideEffects, IBool firstAmbiguity, ISet filters) { RascalTypeFactory rtf = RascalTypeFactory.getInstance(); @@ -246,6 +279,42 @@ public IFunction parsers(IValue reifiedGrammar, IBool allowAmbiguity, IBool hasS return function(functionType, new ParametrizedParseFunction(() -> getParserGenerator(), this, caller, parser, allowAmbiguity, hasSideEffects, firstAmbiguity, filters)); } + @Override + public void storeParsers(IValue reifiedGrammar, ISourceLocation saveLocation) throws IOException { + IMap grammar = (IMap) ((IConstructor) reifiedGrammar).get("definitions"); + getParserGenerator().writeNewParser(new NullRascalMonitor(), URIUtil.rootLocation("parser-generator"), "$GENERATED_PARSER$" + Math.abs(grammar.hashCode()), grammar, saveLocation); + } + + @Override + public IFunction loadParsers(ISourceLocation saveLocation, IBool allowAmbiguity, IBool hasSideEffects,IBool firstAmbiguity, ISet filters) throws IOException, ClassNotFoundException { + RascalTypeFactory rtf = RascalTypeFactory.getInstance(); + TypeFactory tf = TypeFactory.getInstance(); + + // here the return type is parametrized and instantiated when the parser function is called with the + // given start non-terminal: + Type parameterType = tf.parameterType("U", RascalValueFactory.Tree); + + Type functionType = tf.functionType(parameterType, + tf.tupleType(rtf.reifiedType(parameterType), tf.valueType(), tf.sourceLocationType()), + tf.tupleEmpty()); + + final Class> parser + = (Class>) ctx.getEvaluator() + .__getJavaBridge().loadClass(URIResolverRegistry.getInstance().getInputStream(saveLocation)); + + AbstractAST current = ctx.getCurrentAST(); + ISourceLocation caller = current != null ? current.getLocation() : URIUtil.rootLocation("unknown"); + + return function( + functionType, + new ParametrizedParseFunction(() -> getParserGenerator(), + this, + caller, + parser, + allowAmbiguity, hasSideEffects, firstAmbiguity, filters) + ); + } + /** * This function mimicks `parsers(#start[Module]) inside lang::rascal::\syntax::Rascal. * so it produces a parse function for the Rascal language, where non-terminal is the @@ -487,7 +556,10 @@ public IValue apply(IValue[] parameters, Map keywordParameters) throw fail(parameters); } - String name = generator.get().getParserMethodName(startSort); + String name = getParserMethodName(startSort); + if (name == null) { + name = generator.get().getParserMethodName(startSort); + } if (firstAmbiguity) { if (parameters[1].getType().isString()) {