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

Adding JSGLR1 placeholder inference for code completion #91

Draft
wants to merge 12 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,53 @@
import org.checkerframework.checker.nullness.qual.Nullable;

import java.io.Serializable;
import java.util.Objects;

/**
* JSGLR parser input.
*/
public final class JsglrParseInput implements Serializable {

public class JsglrParseInput implements Serializable {
public final Text text;
public final String startSymbol;
public final @Nullable ResourceKey fileHint;
public final @Nullable ResourcePath rootDirectoryHint;
public final boolean codeCompletionMode;
public final int cursorOffset;

public JsglrParseInput(Text text, String startSymbol, @Nullable ResourceKey fileHint, @Nullable ResourcePath rootDirectoryHint) {
/**
* Initializes a new instance of the {@link JsglrParseInput} class.
*
* @param text the text to be parsed
* @param startSymbol the start symbol of the grammar
* @param fileHint a hint of the file being parsed; or {@code null} when not specified
* @param rootDirectoryHint a hint of the project root directory; or {@code null} when not specified
* @param codeCompletionMode whether to parse in completion mode
* @param cursorOffset the zero-based cursor offset in the text
*/
public JsglrParseInput(
Text text,
String startSymbol,
@Nullable ResourceKey fileHint,
@Nullable ResourcePath rootDirectoryHint,
boolean codeCompletionMode,
int cursorOffset
) {
this.text = text;
this.startSymbol = startSymbol;
this.fileHint = fileHint;
this.rootDirectoryHint = rootDirectoryHint;
this.codeCompletionMode = codeCompletionMode;
this.cursorOffset = cursorOffset;
}

public JsglrParseInput(
Text text,
String startSymbol,
@Nullable ResourceKey fileHint,
@Nullable ResourcePath rootDirectoryHint
) {
this(text, startSymbol, fileHint, rootDirectoryHint, false, 0);
}

public JsglrParseInput(Text text, String startSymbol, @Nullable ResourceKey fileHint) {
Expand All @@ -44,18 +79,23 @@ public JsglrParseInput(String text, String startSymbol) {
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
final JsglrParseInput that = (JsglrParseInput)o;
if(!text.equals(that.text)) return false;
if(!startSymbol.equals(that.startSymbol)) return false;
if(fileHint != null ? !fileHint.equals(that.fileHint) : that.fileHint != null) return false;
return rootDirectoryHint != null ? rootDirectoryHint.equals(that.rootDirectoryHint) : that.rootDirectoryHint == null;
return this.text.equals(that.text)
&& this.startSymbol.equals(that.startSymbol)
&& Objects.equals(this.fileHint, that.fileHint)
&& Objects.equals(this.rootDirectoryHint, that.rootDirectoryHint)
&& this.codeCompletionMode == that.codeCompletionMode
&& this.cursorOffset == that.cursorOffset;
}

@Override public int hashCode() {
int result = text.hashCode();
result = 31 * result + startSymbol.hashCode();
result = 31 * result + (fileHint != null ? fileHint.hashCode() : 0);
result = 31 * result + (rootDirectoryHint != null ? rootDirectoryHint.hashCode() : 0);
return result;
return Objects.hash(
text,
startSymbol,
fileHint,
rootDirectoryHint,
codeCompletionMode,
cursorOffset
);
}

@Override public String toString() {
Expand All @@ -64,6 +104,8 @@ public JsglrParseInput(String text, String startSymbol) {
", startSymbol='" + startSymbol + '\'' +
", fileHint=" + fileHint +
", rootDirectoryHint=" + rootDirectoryHint +
", codeCompletionMode=" + codeCompletionMode +
", cursorOffset=" + cursorOffset +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ protected abstract Result<JsglrParseOutput, JsglrParseException> parse(
Text text,
@Nullable String startSymbol,
@Nullable ResourceKey fileHint,
@Nullable ResourcePath rootDirectoryHint
@Nullable ResourcePath rootDirectoryHint,
boolean codeCompletionMode,
int cursorOffset
) throws IOException, InterruptedException;


Expand All @@ -98,8 +100,10 @@ public Result<JsglrParseOutput, JsglrParseException> exec(ExecContext context, J
final @Nullable String startSymbol = input.startSymbol().orElse(null);
final @Nullable ResourceKey fileHint = input.fileHint().getOr(null);
final @Nullable ResourcePath rootDirectoryHint = input.rootDirectoryHint().orElse(null);
final boolean codeCompletionMode = input.codeCompletionMode();
final int cursorOffset = input.cursorOffset();
try {
return parse(context, context.require(input.textSupplier()), startSymbol, fileHint, rootDirectoryHint);
return parse(context, context.require(input.textSupplier()), startSymbol, fileHint, rootDirectoryHint, codeCompletionMode, cursorOffset);
} catch(UncheckedIOException e) {
return Result.ofErr(JsglrParseException.readStringFail(e.getCause(), startSymbol, fileHint, rootDirectoryHint));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,8 @@ static Builder builder(JsglrParseTaskDef parse) {
}

Optional<ResourcePath> rootDirectoryHint();

@Value.Default default boolean codeCompletionMode() { return false; }

@Value.Default default int cursorOffset() { return Integer.MAX_VALUE; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public JSGLR1Parser(JSGLR1ParseTable parseTable, ITermFactory termFactory) {

public JsglrParseOutput parse(JsglrParseInput input) throws JsglrParseException, InterruptedException {
try {
parser.setCompletionParse(input.codeCompletionMode, input.cursorOffset);
final SGLRParseResult result = parser.parse(input.text.toString(), input.fileHint != null ? input.fileHint.toString() : null, input.startSymbol);
if(result.output == null) {
throw new RuntimeException("BUG: parser returned null output even though parsing did not fail");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ public class Spoofax2ParserLanguageCompiler implements TaskDef<Spoofax2ParserLan
public ListView<String> getCopyResources(Input input) {
return ListView.of(
input.parseTableAtermFileRelativePath(),
input.parseTablePersistedFileRelativePath()
input.parseTablePersistedFileRelativePath(),
input.completionParseTableAtermFileRelativePath(),
input.completionParseTablePersistedFileRelativePath()
);
}

Expand Down Expand Up @@ -61,10 +63,28 @@ class Builder extends Spoofax2ParserLanguageCompilerData.Input.Builder {}
return "target/metaborg/table.bin";
}

/**
* @return path to the completion parse table aterm file to copy, relative to the Spoofax 2 language specification project
* directory.
*/
@Value.Default default String completionParseTableAtermFileRelativePath() {
return "target/metaborg/sdf-completions.tbl";
}

/**
* @return path to the completion parse table persisted file to copy, relative to the Spoofax 2 language specification
* project directory.
*/
@Value.Default default String completionParseTablePersistedFileRelativePath() {
return "target/metaborg/table-completions.bin";
}


default void syncTo(ParserLanguageCompiler.Input.Builder builder) {
builder.parseTableAtermFileRelativePath(parseTableAtermFileRelativePath());
builder.parseTablePersistedFileRelativePath(parseTablePersistedFileRelativePath());
builder.completionParseTableAtermFileRelativePath(completionParseTableAtermFileRelativePath());
builder.completionParseTablePersistedFileRelativePath(completionParseTablePersistedFileRelativePath());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ class Builder extends AdapterProjectCompilerData.Input.Builder {}
});
codeCompletion().ifPresent((i) -> {
taskDefs.add(i.codeCompletionTaskDef(), i.baseCodeCompletionTaskDef());
taskDefs.add(i.astWithPlaceholdersTaskDef(), i.baseAstWithPlaceholdersTaskDef());
taskDefs.add(i.statixSpecTaskDef(), i.baseStatixSpecTaskDef());
});
referenceResolution().ifPresent((i) -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
@Value.Enclosing
public class CodeCompletionAdapterCompiler {
private final TemplateWriter codeCompletionTaskDefTemplate;
private final TemplateWriter astWithPlaceholdersTaskDefTemplate;
private final TemplateWriter statixSpecTaskDefTemplate;

/**
Expand All @@ -34,13 +35,15 @@ public class CodeCompletionAdapterCompiler {
@Inject public CodeCompletionAdapterCompiler(TemplateCompiler templateCompiler) {
templateCompiler = templateCompiler.loadingFromClass(getClass());
this.codeCompletionTaskDefTemplate = templateCompiler.getOrCompileToWriter("code_completion/CodeCompletionTaskDef.java.mustache");
this.astWithPlaceholdersTaskDefTemplate = templateCompiler.getOrCompileToWriter("code_completion/AstWithPlaceholdersTaskDef.java.mustache");
this.statixSpecTaskDefTemplate = templateCompiler.getOrCompileToWriter("code_completion/StatixSpecTaskDef.java.mustache");
}

public None compile(ExecContext context, Input input) throws IOException {
if(input.classKind().isManual()) return None.instance; // Nothing to generate: return.
final ResourcePath generatedJavaSourcesDirectory = input.generatedJavaSourcesDirectory();
codeCompletionTaskDefTemplate.write(context, input.baseCodeCompletionTaskDef().file(generatedJavaSourcesDirectory), input);
astWithPlaceholdersTaskDefTemplate.write(context, input.baseAstWithPlaceholdersTaskDef().file(generatedJavaSourcesDirectory), input);
statixSpecTaskDefTemplate.write(context, input.baseStatixSpecTaskDef().file(generatedJavaSourcesDirectory), input);
return None.instance;
}
Expand Down Expand Up @@ -84,6 +87,18 @@ default TypeInfo codeCompletionTaskDef() {
return extendCompleteTaskDef().orElseGet(this::baseCodeCompletionTaskDef);
}

// AST-with-placeholders task definition

@Value.Default default TypeInfo baseAstWithPlaceholdersTaskDef() {
return TypeInfo.of(adapterProject().taskPackageId(), shared().defaultClassPrefix() + "AstWithPlaceholdersTaskDef");
}

Optional<TypeInfo> extendAstWithPlaceholdersTaskDef();

default TypeInfo astWithPlaceholdersTaskDef() {
return extendAstWithPlaceholdersTaskDef().orElseGet(this::baseAstWithPlaceholdersTaskDef);
}

// Statix Spec task definition

@Value.Default default TypeInfo baseStatixSpecTaskDef() {
Expand Down Expand Up @@ -125,6 +140,7 @@ default ListView<ResourcePath> javaSourceFiles() {
final ResourcePath generatedJavaSourcesDirectory = generatedJavaSourcesDirectory();
return ListView.of(
baseCodeCompletionTaskDef().file(generatedJavaSourcesDirectory),
baseAstWithPlaceholdersTaskDef().file(generatedJavaSourcesDirectory),
baseStatixSpecTaskDef().file(generatedJavaSourcesDirectory)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ public class ParserLanguageCompiler {
private final TemplateWriter tableTemplate;
private final TemplateWriter parserTemplate;
private final TemplateWriter factoryTemplate;
private final TemplateWriter completionFactoryTemplate;

@Inject public ParserLanguageCompiler(TemplateCompiler templateCompiler) {
templateCompiler = templateCompiler.loadingFromClass(getClass());
this.tableTemplate = templateCompiler.getOrCompileToWriter("parser/ParseTable.java.mustache");
this.parserTemplate = templateCompiler.getOrCompileToWriter("parser/Parser.java.mustache");
this.factoryTemplate = templateCompiler.getOrCompileToWriter("parser/ParserFactory.java.mustache");
this.completionFactoryTemplate = templateCompiler.getOrCompileToWriter("parser/CompletionParserFactory.java.mustache");
}


Expand All @@ -37,6 +39,7 @@ public None compile(ExecContext context, Input input) throws IOException {
tableTemplate.write(context, input.baseParseTable().file(generatedJavaSourcesDirectory), input);
parserTemplate.write(context, input.baseParser().file(generatedJavaSourcesDirectory), input);
factoryTemplate.write(context, input.baseParserFactory().file(generatedJavaSourcesDirectory), input);
completionFactoryTemplate.write(context, input.baseCompletionParserFactory().file(generatedJavaSourcesDirectory), input);
return None.instance;
}

Expand Down Expand Up @@ -74,6 +77,16 @@ class Builder extends ParserLanguageCompilerData.Input.Builder {}
*/
String parseTablePersistedFileRelativePath();

/**
* @return path to the completion parse table aterm file to load, relative to the classloader resources.
*/
String completionParseTableAtermFileRelativePath();

/**
* @return path to the completion parse table persisted file to load, relative to the classloader resources.
*/
String completionParseTablePersistedFileRelativePath();

@Value.Default default ParserVariant variant() { return ParserVariant.jsglr1(); }


Expand Down Expand Up @@ -126,6 +139,30 @@ default TypeInfo parserFactory() {
return extendParserFactory().orElseGet(this::baseParserFactory);
}

// Completion Parser

@Value.Default default TypeInfo baseCompletionParser() {
return TypeInfo.of(languageProject().packageId(), shared().defaultClassPrefix() + "Parser");
}

Optional<TypeInfo> extendCompletionParser();

default TypeInfo completionParser() {
return extendCompletionParser().orElseGet(this::baseCompletionParser);
}

// Completion Parser factory

@Value.Default default TypeInfo baseCompletionParserFactory() {
return TypeInfo.of(languageProject().packageId(), shared().defaultClassPrefix() + "CompletionParserFactory");
}

Optional<TypeInfo> extendCompletionParserFactory();

default TypeInfo completionParserFactory() {
return extendCompletionParserFactory().orElseGet(this::baseCompletionParserFactory);
}


/// Mustache template helpers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ public class {{baseInstance.id}} implements LanguageInstance

@Override public Task<Option<Styling>> createStyleTask(ResourceKey file, @Nullable ResourcePath rootDirectoryHint) {
{{#parseInjection}}
return {{styleInjection.name}}.createTask({{this.name}}.inputBuilder().withFile(file).rootDirectoryHint(Optional.ofNullable(rootDirectoryHint)).buildRecoverableTokensSupplier().map(TokensResultToOkFunction.instance));
return {{styleInjection.name}}.createTask({{this.name}}.inputBuilder()
.withFile(file)
.rootDirectoryHint(Optional.ofNullable(rootDirectoryHint))
.buildRecoverableTokensSupplier()
.map(TokensResultToOkFunction.instance)
);
{{/parseInjection}}
{{^parseInjection}}
return {{styleInjection.name}}.createTask((ctx) -> mb.pie.api.None.instance);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ public class {{baseModule.id}} {
return parserFactory.create();
}

@Provides @{{scope.id}} @Named("completion")
static {{this.languageProjectInput.completionParserFactory.qualifiedId}} provideCompletionParserFactory(@{{qualifier.id}}("definition-directory") HierarchicalResource definitionDir) {
return new {{this.languageProjectInput.completionParserFactory.qualifiedId}}(definitionDir);
}

@Provides @Named("completion") /* Unscoped: parser has state, so create a new parser every call. */
static {{this.languageProjectInput.completionParser.qualifiedId}} provideCompletionParser(@Named("completion") {{this.languageProjectInput.completionParserFactory.qualifiedId}} parserFactory) {
return parserFactory.create();
}

@Provides @{{scope.id}}
static ITermFactory provideTermFactory() {
return new org.spoofax.jsglr.client.imploder.ImploderOriginTermFactory(new TermFactory());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package {{baseAstWithPlaceholdersTaskDef.packageId}};

import mb.log.api.LoggerFactory;
import mb.statix.codecompletion.pie.AstWithPlaceholdersTaskDef;

import javax.inject.Inject;

@{{adapterProject.scope.qualifiedId}}
public class {{baseAstWithPlaceholdersTaskDef.id}} extends AstWithPlaceholdersTaskDef {

@Inject
public {{baseAstWithPlaceholdersTaskDef.id}}(
{{strategoRuntimeInput.getStrategoRuntimeProviderTaskDef.qualifiedId}} getStrategoRuntimeProviderTask,
LoggerFactory loggerFactory
) {
super(
getStrategoRuntimeProviderTask,
loggerFactory
);
}

@Override
public String getId() {
return "{{baseAstWithPlaceholdersTaskDef.id}}";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import mb.log.api.LoggerFactory;
import mb.nabl2.terms.stratego.StrategoTerms;
import mb.pie.api.ExecContext;
import mb.pie.api.stamp.resource.ResourceStampers;
import mb.statix.codecompletion.pie.CodeCompletionEventHandlerBase;
import mb.statix.codecompletion.pie.AstWithPlaceholdersTaskDef;
import mb.statix.codecompletion.pie.CodeCompletionTaskDef;
import mb.tego.strategies.runtime.TegoRuntime;
import org.checkerframework.checker.nullness.qual.Nullable;
Expand All @@ -22,6 +22,7 @@ public class {{baseCodeCompletionTaskDef.id}} extends CodeCompletionTaskDef {
public {{baseCodeCompletionTaskDef.id}}(
{{parserInput.parseTaskDef.qualifiedId}} parseTask,
{{constraintAnalyzerInput.analyzeFileTaskDef.qualifiedId}} analyzeFileTask,
{{astWithPlaceholdersTaskDef.qualifiedId}} astWithPlaceholdersTaskDef,
{{strategoRuntimeInput.getStrategoRuntimeProviderTaskDef.qualifiedId}} getStrategoRuntimeProviderTask,
TegoRuntime tegoRuntime,
{{statixSpecTaskDef.qualifiedId}} statixSpec,
Expand All @@ -32,6 +33,7 @@ public class {{baseCodeCompletionTaskDef.id}} extends CodeCompletionTaskDef {
super(
parseTask,
analyzeFileTask,
astWithPlaceholdersTaskDef,
getStrategoRuntimeProviderTask,
tegoRuntime,
statixSpec,
Expand Down
Loading