Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow DSLs deployments to load a parser from the precompiled file without waiting for evaluators to load #297

Merged
merged 16 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ set -euxo pipefail

extra_flags=''

while getopts 'f' flag; do
clean="clean"
while getopts 'fd' flag; do
case "${flag}" in
f) extra_flags='-Drascal.compile.skip' ;;
*) printf "Use -f to skip rascal-compile"
d) clean='' ;;
*) printf "incorrect param, valid params:
Use -f to skip rascal-compile
Use -d to skip cleaning the target folder"
exit 1 ;;
esac
done

rm rascal-lsp/target/*.jar

(cd rascal-lsp && mvn clean package $extra_flags )
(cd rascal-lsp && mvn $clean package $extra_flags )
(cd rascal-vscode-extension && npm run lsp4j:package )

Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ public void registerLanguage(IConstructor language) {
((IString) language.get(1)).getValue(),
((IString) language.get(2)).getValue(),
((IString) language.get(3)).getValue(),
((IString) language.get(4)).getValue()
((IString) language.get(4)).getValue(),
null
);

languageClient.receiveRegisterLanguage(param);
Expand All @@ -158,7 +159,8 @@ public void unregisterLanguage(IConstructor language) {
((IString) language.get(1)).getValue(),
((IString) language.get(2)).getValue(),
((IString) language.get(3)).getValue(),
((IString) language.get(4)).getValue()
((IString) language.get(4)).getValue(),
null
);

languageClient.receiveUnregisterLanguage(param);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ public void addContributor(String contribKey, ILanguageContributions contrib) {
* @returns false if the multiplexer is empty, and therefore should not be used anymore
*/
public boolean removeContributor(String contribKey) {
contributions.removeIf(e -> e.key.equals(contribKey));
contributions.removeIf(e -> e.key.equals(contribKey) || e.key.equals(contribKey + "$parser"));
if (contributions.isEmpty()) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
import io.usethesource.vallang.ITuple;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IWithKeywordParameters;
import io.usethesource.vallang.exceptions.FactParseError;

// suppress required due to forced usage of deprecated `SymbolInformation` class in `Either` until LSP4J cleans it up:
@SuppressWarnings({"deprecation"})
Expand Down Expand Up @@ -534,6 +535,21 @@ public void registerLanguage(LanguageParameter lang) {
var fact = facts.computeIfAbsent(lang.getExtension(), t ->
new ParametricFileFacts(multiplexer, this::getFile, columns, ownExecuter)
);
if (lang.getPrecompiledParser() != null) {
try {
var location = lang.getPrecompiledParser().getParserLocation();
if (URIResolverRegistry.getInstance().exists(location)) {
logger.debug("Got precompiled definition: {}", lang.getPrecompiledParser());
multiplexer.addContributor(buildContributionKey(lang) + "$parser", new ParserOnlyContribution(lang.getName(), lang.getExtension(), lang.getPrecompiledParser()));
}
else {
logger.error("Defined precompiled parser ({}) does not exist", lang.getPrecompiledParser());
}
}
catch (FactParseError e) {
logger.error("Error parsing location in precompiled parser specification (we expect a rascal loc)", e);
}
}

multiplexer.addContributor(buildContributionKey(lang),
new InterpretedLanguageContributions(lang, this, workspaceService, (IBaseLanguageClient) client, ownExecuter));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
* Copyright (c) 2018-2023, NWO-I CWI and Swat.engineering
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package org.rascalmpl.vscode.lsp.parametric;

import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.rascalmpl.exceptions.RuntimeExceptionFactory;
import org.rascalmpl.interpreter.Configuration;
import org.rascalmpl.interpreter.Evaluator;
import org.rascalmpl.interpreter.asserts.Ambiguous;
import org.rascalmpl.interpreter.staticErrors.UndeclaredNonTerminal;
import org.rascalmpl.interpreter.utils.JavaBridge;
import org.rascalmpl.parser.gtd.IGTD;
import org.rascalmpl.parser.gtd.exception.ParseError;
import org.rascalmpl.parser.gtd.exception.UndeclaredNonTerminalException;
import org.rascalmpl.parser.gtd.recovery.IRecoverer;
import org.rascalmpl.parser.gtd.result.out.DefaultNodeFlattener;
import org.rascalmpl.parser.uptr.UPTRNodeFactory;
import org.rascalmpl.parser.uptr.action.NoActionExecutor;
import org.rascalmpl.uri.URIResolverRegistry;
import org.rascalmpl.values.IRascalValueFactory;
import org.rascalmpl.values.parsetrees.ITree;
import org.rascalmpl.values.parsetrees.SymbolAdapter;
import org.rascalmpl.values.parsetrees.TreeAdapter;
import org.rascalmpl.vscode.lsp.parametric.model.ParametricSummaryBridge;
import org.rascalmpl.vscode.lsp.terminal.ITerminalIDEServer.ParserSpecification;
import org.rascalmpl.vscode.lsp.util.concurrent.InterruptibleFuture;
import io.usethesource.vallang.IConstructor;
import io.usethesource.vallang.IList;
import io.usethesource.vallang.ISet;
import io.usethesource.vallang.ISourceLocation;
import io.usethesource.vallang.IValue;
import io.usethesource.vallang.IValueFactory;

public class ParserOnlyContribution implements ILanguageContributions {

private final String name;
private final String extension;
private final @Nullable Exception loadingParserError;
private final @Nullable Class<IGTD<IConstructor, ITree, ISourceLocation>> parserClass;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why leak these implementation details if Prelude.loadParsers hides all of that? All you have to do is call that function with the right source location. You receive an IFunction that can be called directly with an input source location or string (first parameter), then the location used for the @loc annotations. The options are provided with the loadParsers method. If you don't want to instantiate a Prelude, a RascalFunctionValueFactory instance with an empty Evaluator for the constructor will also do fine.

With this you have saved 130 lines of code, and also you can hide behind the interface in case future parser implementations change the way they are implemented.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the IFunction is acquired, there is no more need for the RascalFunctionValueFactory and its empty Evaluator, or a Prelude instance. The function instance will lock on the evaluator only when/if it is called from the evaluator, and not if called manually via the IFunction.call interface.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to copy that code since it had a hard dependency on Evaluator. I specificaly wanted not to load an evaluator.

private final String parserMethodName;
private final boolean allowAmbiguity;

public ParserOnlyContribution(String name, String extension, ParserSpecification spec) {
this.name = name;
this.extension = extension;
Class<IGTD<IConstructor, ITree, ISourceLocation>> clzz = null;
Exception err = null;
try {
clzz = loadParserClass(spec);
} catch (ClassNotFoundException | IOException e) {
err = e;
}
this.parserClass = clzz;
this.loadingParserError = err;
this.parserMethodName = (spec.getNonTerminalIsStart() ? "start__" : "") + spec.getNonTerminalName();
this.allowAmbiguity = spec.getAllowAmbiguity();
}

@Override
public String getName() {
return name;
}

@Override
public String getExtension() {
return extension;
}

@Override
public CompletableFuture<ITree> parseSourceFile(ISourceLocation loc, String input) {
DavyLandman marked this conversation as resolved.
Show resolved Hide resolved
if (parserClass == null || loadingParserError != null) {
return CompletableFuture.failedFuture(new RuntimeException("ParserClass did not load", loadingParserError));
}
return CompletableFuture.supplyAsync(() -> {
try {
IGTD<IConstructor, ITree, ISourceLocation> parser
= parserClass.getDeclaredConstructor().newInstance();
return (ITree)parser.parse(
parserMethodName, loc.getURI(), input.toCharArray(), new NoActionExecutor(),
new DefaultNodeFlattener<>(),
new UPTRNodeFactory(allowAmbiguity),
(IRecoverer<IConstructor>) null
);
}
catch (ParseError pe) {
ISourceLocation errorLoc = pe.getLocation();
throw RuntimeExceptionFactory.parseError(errorLoc);
}
catch (Ambiguous e) {
ITree tree = e.getTree();
IValueFactory vf = IRascalValueFactory.getInstance();
throw RuntimeExceptionFactory.ambiguity(e.getLocation(), vf.string(SymbolAdapter.toString(TreeAdapter.getType(tree), false)), vf.string(TreeAdapter.yield(tree)));
}
catch (UndeclaredNonTerminalException e){
throw new UndeclaredNonTerminal(e.getName(), e.getClassName(), loc);
}
catch (ReflectiveOperationException | IllegalArgumentException | SecurityException e) {
throw new CompletionException("Error with loaded parser", e);
}
});
}

@SuppressWarnings("unchecked")
private static Class<IGTD<IConstructor, ITree, ISourceLocation>> loadParserClass(ParserSpecification spec) throws ClassNotFoundException, IOException {
DavyLandman marked this conversation as resolved.
Show resolved Hide resolved
var bridge = new JavaBridge(
Collections.singletonList(Evaluator.class.getClassLoader()),
IRascalValueFactory.getInstance(),
new Configuration());
return (Class<IGTD<IConstructor, ITree, ISourceLocation>>)bridge.loadClass(URIResolverRegistry.getInstance().getInputStream(spec.getParserLocation()));
}



private static final IValueFactory VF = IRascalValueFactory.getInstance();

@Override
public InterruptibleFuture<IList> outline(ITree input) {
return InterruptibleFuture.completedFuture(VF.list());
}

@Override
public InterruptibleFuture<IConstructor> summarize(ISourceLocation loc, ITree input) {
return InterruptibleFuture.completedFuture(ParametricSummaryBridge.emptySummary(loc));
}

@Override
public InterruptibleFuture<ISet> lenses(ITree input) {
return InterruptibleFuture.completedFuture(VF.set());
}

@Override
public InterruptibleFuture<@Nullable IValue> executeCommand(String command) {
return InterruptibleFuture.completedFuture(VF.bool(false));
}

@Override
public InterruptibleFuture<IList> inlayHint(@Nullable ITree input) {
return InterruptibleFuture.completedFuture(VF.list());
}

@Override
public InterruptibleFuture<ISet> documentation(ISourceLocation loc, ITree input, ITree cursor) {
return InterruptibleFuture.completedFuture(VF.set());
}

@Override
public InterruptibleFuture<ISet> defines(ISourceLocation loc, ITree input, ITree cursor) {
return InterruptibleFuture.completedFuture(VF.set());
}

@Override
public InterruptibleFuture<ISet> references(ISourceLocation loc, ITree input, ITree cursor) {
return InterruptibleFuture.completedFuture(VF.set());
}

@Override
public InterruptibleFuture<ISet> implementations(ISourceLocation loc, ITree input, ITree cursor) {
return InterruptibleFuture.completedFuture(VF.set());
}

@Override
public CompletableFuture<Boolean> hasDedicatedDocumentation() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasDedicatedDefines() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasDedicatedReferences() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasDedicatedImplementations() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasOutline() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasSummarize() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasLenses() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasExecuteCommand() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> hasInlayHint() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> askSummaryForDocumentation() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> askSummaryForDefinitions() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> askSummaryForReferences() {
return CompletableFuture.completedFuture(false);
}

@Override
public CompletableFuture<Boolean> askSummaryForImplementations() {
return CompletableFuture.completedFuture(false);
}

}
Loading
Loading