diff --git a/lwb/metalang/cfg/cfg.spoofax2/syntax/part/language.sdf3 b/lwb/metalang/cfg/cfg.spoofax2/syntax/part/language.sdf3 index e2b8cb01b..eb933d950 100644 --- a/lwb/metalang/cfg/cfg.spoofax2/syntax/part/language.sdf3 +++ b/lwb/metalang/cfg/cfg.spoofax2/syntax/part/language.sdf3 @@ -8,7 +8,7 @@ imports context-free sorts Sdf3Option Sdf3Source Sdf3FilesOption Sdf3PrebuiltOption - Sdf3ParseTableGeneratorOption + Sdf3ParseTableGeneratorOption Sdf3PlaceholderOption context-free syntax @@ -26,6 +26,9 @@ context-free syntax Sdf3FilesOption.Sdf3FilesExportDirectory = > Sdf3FilesOption.Sdf3ParseTableGeneratorSection = +}> + Sdf3FilesOption.Sdf3PlaceholderSection = }> Sdf3FilesOption.Sdf3StrategoConcreteSyntaxExtensionMainFile = > @@ -36,6 +39,9 @@ context-free syntax Sdf3ParseTableGeneratorOption.Sdf3ParseTableGeneratorCheckOverlap = > Sdf3ParseTableGeneratorOption.Sdf3ParseTableGeneratorCheckPriorities = > + Sdf3PlaceholderOption.Sdf3PlaceholderPrefix = > + Sdf3PlaceholderOption.Sdf3PlaceholderPostfix = > + Sdf3Source.Sdf3Prebuilt = }> diff --git a/lwb/metalang/cfg/cfg.spoofax2/trans/statsem/part/language.stx b/lwb/metalang/cfg/cfg.spoofax2/trans/statsem/part/language.stx index c9f786122..07bb63c05 100644 --- a/lwb/metalang/cfg/cfg.spoofax2/trans/statsem/part/language.stx +++ b/lwb/metalang/cfg/cfg.spoofax2/trans/statsem/part/language.stx @@ -34,6 +34,8 @@ rules // Sdf3 section and options typeOfExpr(s, e) == PATH() | error $[Expected path]@e. sdf3FilesOptionOk(s, Sdf3ParseTableGeneratorSection(options)) :- sdf3ParseTableGeneratorOptionsOk(s, options). + sdf3FilesOptionOk(s, Sdf3PlaceholderSection(e)) :- + sdf3PlaceholderOptionsOk(s, e). sdf3FilesOptionOk(s, Sdf3StrategoConcreteSyntaxExtensionMainFile(e)) :- typeOfExpr(s, e) == PATH() | error $[Expected path]@e. @@ -53,6 +55,14 @@ rules // Sdf3 section and options sdf3ParseTableGeneratorOptionOk(s, Sdf3ParseTableGeneratorCheckPriorities(e)) :- typeOfExpr(s, e) == BOOL() | error $[Expected boolean]@e. + sdf3PlaceholderOptionOk : scope * Sdf3PlaceholderOption + sdf3PlaceholderOptionsOk maps sdf3PlaceholderOptionOk(*, list(*)) + + sdf3PlaceholderOptionOk(s, Sdf3PlaceholderPrefix(e)) :- + typeOfExpr(s, e) == STRING() | error $[Expected string]@e. + sdf3PlaceholderOptionOk(s, Sdf3PlaceholderPostfix(e)) :- + typeOfExpr(s, e) == STRING() | error $[Expected string]@e. + sdf3PrebuiltOptionOk : scope * Sdf3PrebuiltOption sdf3PrebuiltOptionsOk maps sdf3PrebuiltOptionOk(*, list(*)) sdf3PrebuiltOptionOk(s, Sdf3PrebuiltParseTableAtermFile(e)) :- diff --git a/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/convert/CfgAstToObject.java b/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/convert/CfgAstToObject.java index 28fc544d9..355837a76 100644 --- a/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/convert/CfgAstToObject.java +++ b/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/convert/CfgAstToObject.java @@ -181,6 +181,10 @@ public static Output convert( ptgParts.forOneSubtermAsBool("Sdf3ParseTableGeneratorCheckOverlap", filesSourceBuilder::checkOverlapInParseTable); ptgParts.forOneSubtermAsBool("Sdf3ParseTableGeneratorCheckPriorities", filesSourceBuilder::checkPrioritiesInParseTable); }); + filesParts.getAllSubTermsInListAsParts("Sdf3PlaceholderSection").ifSome(ptgParts -> { + ptgParts.forOneSubtermAsString("Sdf3PlaceholderPrefix", filesSourceBuilder::sdf3PlaceholderPrefix); + ptgParts.forOneSubtermAsString("Sdf3PlaceholderPostfix", filesSourceBuilder::sdf3PlaceholderPostfix); + }); filesParts.forAllSubtermsAsExistingFiles("Sdf3StrategoConcreteSyntaxExtensionMainFile", mainSourceDirectory, "SDF3 Stratego concrete syntax extension main file", filesSourceBuilder::addStrategoConcreteSyntaxExtensionMainFiles); builder.source(CfgSdf3Source.files(filesSourceBuilder.build())); } else if(TermUtils.isAppl(source, "Sdf3Prebuilt", 1)) { @@ -280,6 +284,10 @@ public static Output convert( final CfgStrategoSource.Files.Builder filesSourceBuilder = compileMetaLanguageSourcesInputBuilder.withStrategoFilesSource(); final ResourcePath mainSourceDirectory = filesParts.getOneSubtermAsExistingDirectory("StrategoFilesMainSourceDirectory", rootDirectory, "Stratego main source directory") .unwrapOrElse(() -> CfgStrategoSource.Files.Builder.getDefaultMainSourceDirectory(languageShared)); + compileMetaLanguageSourcesInputBuilder.sdf3.compileMetaLanguageSourcesShared(languageShared).build().source().getFiles().ifPresent(f -> { + filesSourceBuilder.sdf3PlaceholderPrefix(f.sdf3PlaceholderPrefix()); + filesSourceBuilder.sdf3PlaceholderPostfix(f.sdf3PlaceholderPostfix()); + }); filesSourceBuilder.mainSourceDirectory(mainSourceDirectory); filesParts.forOneSubtermAsExistingFile("StrategoFilesMainFile", mainSourceDirectory, "Stratego main file", filesSourceBuilder::mainFile); filesParts.forAllSubtermsAsExistingDirectories("StrategoFilesIncludeDirectory", rootDirectory, "Stratego include directory", filesSourceBuilder::addIncludeDirectories); diff --git a/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgSdf3Source.java b/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgSdf3Source.java index d3516b230..d73f2b123 100644 --- a/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgSdf3Source.java +++ b/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgSdf3Source.java @@ -1,10 +1,12 @@ package mb.cfg.metalang; import mb.cfg.CompileMetaLanguageSourcesShared; +import mb.common.option.Option; import mb.common.util.ADT; import mb.resource.hierarchical.ResourcePath; import org.checkerframework.checker.nullness.qual.Nullable; import org.immutables.value.Value; +import org.metaborg.util.tuple.Tuple2; import java.io.Serializable; import java.util.List; @@ -69,6 +71,20 @@ default ResourcePath unarchiveDirectory() { return false; } + @Value.Default default @Nullable String sdf3PlaceholderPrefix() { return null; } + + @Value.Default default @Nullable String sdf3PlaceholderPostfix() { return null; } + + @Value.Derived default Option> sdf3Placeholders() { + final @Nullable String prefix = sdf3PlaceholderPrefix(); + final @Nullable String suffix = sdf3PlaceholderPostfix(); + if(prefix != null || suffix != null) { + return Option.ofSome(Tuple2.of(prefix != null ? prefix : "", suffix != null ? suffix : "")); + } else { + return Option.ofNone(); + } + } + /// Automatically provided sub-inputs diff --git a/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgStrategoSource.java b/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgStrategoSource.java index 2f336ccd3..b5ebeb32a 100644 --- a/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgStrategoSource.java +++ b/lwb/metalang/cfg/cfg/src/main/java/mb/cfg/metalang/CfgStrategoSource.java @@ -1,15 +1,16 @@ package mb.cfg.metalang; import mb.cfg.CompileMetaLanguageSourcesShared; +import mb.common.option.Option; import mb.common.util.ADT; import mb.resource.hierarchical.ResourcePath; import mb.spoofax.compiler.adapter.ExportsCompiler; -import mb.spoofax.compiler.language.StrategoRuntimeLanguageCompiler; import mb.spoofax.compiler.util.BuilderBase; import mb.spoofax.compiler.util.Conversion; import mb.spoofax.compiler.util.Shared; import org.checkerframework.checker.nullness.qual.Nullable; import org.immutables.value.Value; +import org.metaborg.util.tuple.Tuple2; import java.io.Serializable; import java.util.ArrayList; @@ -79,6 +80,20 @@ public static ResourcePath getDefaultMainSourceDirectory(CompileMetaLanguageSour return false; } + @Value.Default default @Nullable String sdf3PlaceholderPrefix() { return null; } + + @Value.Default default @Nullable String sdf3PlaceholderPostfix() { return null; } + + @Value.Derived default Option> sdf3Placeholders() { + final @Nullable String prefix = sdf3PlaceholderPrefix(); + final @Nullable String suffix = sdf3PlaceholderPostfix(); + if(prefix != null || suffix != null) { + return Option.ofSome(Tuple2.of(prefix != null ? prefix : "", suffix != null ? suffix : "")); + } else { + return Option.ofNone(); + } + } + @Value.Default default String languageStrategyAffix() { // TODO: convert to Stratego ID instead of Java ID. return Conversion.nameToJavaId(shared().name().toLowerCase()); diff --git a/lwb/metalang/sdf3/sdf3/build.gradle.kts b/lwb/metalang/sdf3/sdf3/build.gradle.kts index 6cab02cb2..d25929d50 100644 --- a/lwb/metalang/sdf3/sdf3/build.gradle.kts +++ b/lwb/metalang/sdf3/sdf3/build.gradle.kts @@ -234,7 +234,31 @@ fun AdapterProjectCompiler.Input.Builder.configureCompilerInput() { val showDesugarCommand = showCommand(showDesugar, "desugared") val showPermissiveCommand = showCommand(showPermissive, "permissive grammar") - val showNormalFormCommand = showCommand(showNormalForm, "normal-form") + val showNormalFormCommand = CommandDefRepr.builder() + .type(commandPackageId, showNormalForm.id() + "Command") + .taskDefType(showNormalForm) + .argType(showNormalForm.appendToId(".Args")) + .displayName("Show normal-form") + .description("Shows the normal-form of the file") + .addSupportedExecutionTypes(CommandExecutionType.ManualOnce, CommandExecutionType.ManualContinuous) + .addAllParams( + listOf( + ParamRepr.of( + "root", + TypeInfo.of("mb.resource.hierarchical", "ResourcePath"), + true, + ArgProviderRepr.enclosingContext(EnclosingCommandContextType.Project) + ), + ParamRepr.of( + "file", + TypeInfo.of("mb.resource", "ResourceKey"), + true, + ArgProviderRepr.context(CommandContextType.File) + ), + ParamRepr.of("concrete", TypeInfo.ofBoolean(), true) + ) + ) + .build() val showSignatureCommand = showAnalyzedCommand(showSignature, "Stratego signatures") val showDynsemSignatureCommand = showAnalyzedCommand(showDynsemSignature, "DynSem signatures") diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3Context.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3Context.java index 1791d724e..1ef750a7c 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3Context.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3Context.java @@ -1,9 +1,15 @@ package mb.sdf3.stratego; +import org.metaborg.util.tuple.Tuple2; + +import mb.common.option.Option; + public class Sdf3Context { - public final String strategoQualifier; + public final Option strategoQualifier; + public final Option> placeholders; - public Sdf3Context(String strategoQualifier) { + public Sdf3Context(Option strategoQualifier, Option> placeholders) { this.strategoQualifier = strategoQualifier; + this.placeholders = placeholders; } } diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PlaceholderCharsPrimitive.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PlaceholderCharsPrimitive.java new file mode 100644 index 000000000..1954dda9a --- /dev/null +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PlaceholderCharsPrimitive.java @@ -0,0 +1,37 @@ +package mb.sdf3.stratego; + +import java.util.Objects; + +import org.metaborg.util.tuple.Tuple2; +import org.spoofax.interpreter.core.IContext; +import org.spoofax.interpreter.core.InterpreterException; +import org.spoofax.interpreter.library.AbstractPrimitive; +import org.spoofax.interpreter.stratego.Strategy; +import org.spoofax.interpreter.terms.IStrategoTerm; +import org.spoofax.interpreter.terms.ITermFactory; + +import mb.common.option.Option; +import mb.stratego.common.AdaptException; +import mb.stratego.common.AdaptableContext; + +public class Sdf3PlaceholderCharsPrimitive extends AbstractPrimitive { + public Sdf3PlaceholderCharsPrimitive() { + super("SSL_EXT_placeholder_chars", 0, 0); + } + + @Override public boolean call(IContext env, Strategy[] svars, IStrategoTerm[] tvars) throws InterpreterException { + try { + final Sdf3Context context = AdaptableContext.adaptContextObject(env.contextObject(), Sdf3Context.class); + final Option> placeholders = context.placeholders; + if(placeholders.isNone()) { + return false; + } + final Tuple2 placeholderChars = Objects.requireNonNull(placeholders.get()); + final ITermFactory f = env.getFactory(); + env.setCurrent(f.makeTuple(f.makeString(placeholderChars._1()), f.makeString(placeholderChars._2()))); + return true; + } catch(AdaptException e) { + return false; // Context not available; fail + } + } +} diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PpLanguageSpecNamePrimitive.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PpLanguageSpecNamePrimitive.java index 3c76fda98..a97001452 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PpLanguageSpecNamePrimitive.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PpLanguageSpecNamePrimitive.java @@ -15,7 +15,10 @@ public Sdf3PpLanguageSpecNamePrimitive() { @Override public boolean call(IContext env, Strategy[] svars, IStrategoTerm[] tvars) throws InterpreterException { try { final Sdf3Context context = AdaptableContext.adaptContextObject(env.contextObject(), Sdf3Context.class); - env.setCurrent(env.getFactory().makeString(context.strategoQualifier)); + if(context.strategoQualifier.isNone()) { + return false; + } + env.setCurrent(env.getFactory().makeString(context.strategoQualifier.get())); return true; } catch(RuntimeException e) { return false; // Context not available; fail diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PrimitiveLibrary.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PrimitiveLibrary.java index 18d0431e7..cf8d8a0a0 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PrimitiveLibrary.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/stratego/Sdf3PrimitiveLibrary.java @@ -7,7 +7,7 @@ public class Sdf3PrimitiveLibrary extends AbstractStrategoOperatorRegistry { public Sdf3PrimitiveLibrary() { add(new Sdf3PpLanguageSpecNamePrimitive()); - add(new FailingPrimitive("SSL_EXT_placeholder_chars")); + add(new Sdf3PlaceholderCharsPrimitive()); add(new Sdf3SpoofaxVersionPrimitive()); } diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToNormalForm.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToNormalForm.java index 454d39d7f..4aa35cd60 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToNormalForm.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToNormalForm.java @@ -4,6 +4,7 @@ import mb.pie.api.Interactivity; import mb.pie.api.Supplier; import mb.sdf3.Sdf3Scope; +import mb.sdf3.task.util.Sdf3StrategoTransformTaskDef; import mb.stratego.pie.AstStrategoTransformTaskDef; import org.spoofax.interpreter.terms.IStrategoTerm; @@ -11,7 +12,7 @@ import java.util.Set; @Sdf3Scope -public class Sdf3ToNormalForm extends AstStrategoTransformTaskDef { +public class Sdf3ToNormalForm extends Sdf3StrategoTransformTaskDef { @Inject public Sdf3ToNormalForm(Sdf3GetStrategoRuntimeProvider getStrategoRuntimeProvider) { super(getStrategoRuntimeProvider, "module-to-normal-form"); } @@ -20,7 +21,7 @@ public class Sdf3ToNormalForm extends AstStrategoTransformTaskDef { return getClass().getName(); } - @Override public boolean shouldExecWhenAffected(Supplier> input, Set tags) { + @Override public boolean shouldExecWhenAffected(Set tags) { return tags.isEmpty() || tags.contains(Interactivity.NonInteractive); } } diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToPrettyPrinter.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToPrettyPrinter.java index d1595afbb..29aa1c25b 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToPrettyPrinter.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/Sdf3ToPrettyPrinter.java @@ -1,5 +1,6 @@ package mb.sdf3.task; +import mb.common.option.Option; import mb.common.result.Result; import mb.pie.api.ExecContext; import mb.pie.api.Interactivity; @@ -10,6 +11,7 @@ import mb.stratego.common.StrategoException; import mb.stratego.common.StrategoRuntime; import org.checkerframework.checker.nullness.qual.Nullable; +import org.metaborg.util.tuple.Tuple2; import org.spoofax.interpreter.terms.IStrategoTerm; import javax.inject.Inject; @@ -23,27 +25,35 @@ public class Sdf3ToPrettyPrinter implements TaskDef> astSupplier; public final String strategoQualifier; + public final Option> placeholders; - public Input(Supplier> astSupplier, String strategoQualifier) { + public Input(Supplier> astSupplier, String strategoQualifier, + Option> placeholders) { this.astSupplier = astSupplier; this.strategoQualifier = strategoQualifier; + this.placeholders = placeholders; + } + + public Input(Supplier> astSupplier, String strategoQualifier) { + this(astSupplier, strategoQualifier, Option.ofNone()); } @Override public boolean equals(@Nullable Object o) { if(this == o) return true; if(o == null || getClass() != o.getClass()) return false; final Input input = (Input)o; - return astSupplier.equals(input.astSupplier) && strategoQualifier.equals(input.strategoQualifier); + return astSupplier.equals(input.astSupplier) && strategoQualifier.equals(input.strategoQualifier) && placeholders.equals(input.placeholders); } @Override public int hashCode() { - return Objects.hash(astSupplier, strategoQualifier); + return Objects.hash(astSupplier, strategoQualifier, placeholders); } @Override public String toString() { return "Input{" + "astSupplier=" + astSupplier + ", strategoQualifier='" + strategoQualifier + '\'' + + ", placeholders='" + placeholders + '\'' + '}'; } } @@ -60,8 +70,8 @@ public Input(Supplier> astSupplier, String st } @Override public Result exec(ExecContext context, Input input) throws Exception { - final StrategoRuntime strategoRuntime = strategoRuntimeProvider.get().addContextObject(new Sdf3Context(input.strategoQualifier)); return context.require(input.astSupplier).flatMapOrElse((ast) -> { + final StrategoRuntime strategoRuntime = strategoRuntimeProvider.get().addContextObject(new Sdf3Context(Option.ofSome(input.strategoQualifier), input.placeholders)); try { ast = strategoRuntime.invoke("module-to-pp", ast, strategoRuntime.getTermFactory().makeString("2")); return Result.ofOk(ast); diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/debug/Sdf3ShowNormalForm.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/debug/Sdf3ShowNormalForm.java index 8e7ad7181..45776f7b0 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/debug/Sdf3ShowNormalForm.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/debug/Sdf3ShowNormalForm.java @@ -1,22 +1,98 @@ package mb.sdf3.task.debug; +import java.io.Serializable; +import java.util.Objects; + +import mb.pie.api.ExecContext; +import mb.pie.api.TaskDef; +import mb.resource.ResourceKey; +import mb.resource.hierarchical.ResourcePath; import mb.sdf3.Sdf3Scope; import mb.sdf3.task.Sdf3Desugar; import mb.sdf3.task.Sdf3GetStrategoRuntimeProvider; import mb.sdf3.task.Sdf3ToNormalForm; +import mb.sdf3.task.spec.Sdf3SpecConfig; import mb.sdf3.task.spoofax.Sdf3ParseWrapper; +import mb.sdf3.task.spoofax.Sdf3SpecConfigFunctionWrapper; +import mb.sdf3.task.util.Sdf3StrategoTransformTaskDef; +import mb.spoofax.core.language.command.CommandFeedback; +import mb.spoofax.core.language.command.ShowFeedback; import javax.inject.Inject; +import org.checkerframework.checker.nullness.qual.Nullable; + +import static mb.sdf3.task.util.Sdf3StrategoTransformTaskDef.inputSupplier; + @Sdf3Scope -public class Sdf3ShowNormalForm extends ShowTaskDef { +public class Sdf3ShowNormalForm extends ProvideOutputShared implements TaskDef { + public static class Args implements Serializable { + // TODO: this should take a Sdf3SpecConfig directly, which must be assignable from CLI and such, but this is not possible yet. + public final ResourcePath root; + public final ResourceKey file; + public final boolean concrete; + + public Args(ResourcePath root, ResourceKey file, boolean concrete) { + this.root = root; + this.file = file; + this.concrete = concrete; + } + + @Override public boolean equals(Object o) { + if(this == o) + return true; + if(o == null || getClass() != o.getClass()) + return false; + Args args = (Args)o; + return concrete == args.concrete && Objects.equals(root, args.root) && Objects.equals(file, args.file); + } + + @Override public int hashCode() { + return Objects.hash(root, file, concrete); + } + + @Override public String toString() { + return "Args{" + "root=" + root + ", file=" + file + ", concrete=" + concrete + '}'; + } + } + + private final Sdf3SpecConfigFunctionWrapper configFunctionWrapper; + private final Sdf3ParseWrapper parse; + private final Sdf3Desugar desugar; + private final Sdf3ToNormalForm toNormalForm; + @Inject public Sdf3ShowNormalForm( + Sdf3SpecConfigFunctionWrapper configFunctionWrapper, Sdf3ParseWrapper parse, Sdf3Desugar desugar, - Sdf3ToNormalForm operation, + Sdf3ToNormalForm toNormalForm, Sdf3GetStrategoRuntimeProvider getStrategoRuntimeProvider ) { - super(parse, desugar, operation, getStrategoRuntimeProvider, "pp-SDF3-string", "normal-form"); + super(getStrategoRuntimeProvider, "pp-SDF3-string", "normal-form"); + this.configFunctionWrapper = configFunctionWrapper; + this.parse = parse; + this.desugar = desugar; + this.toNormalForm = toNormalForm; + } + + @Override public CommandFeedback exec(ExecContext context, Sdf3ShowNormalForm.Args args) { + final String name = "Parse table for project '" + args.root + "'"; + return context.require(configFunctionWrapper.get(), args.root).mapOrElse( + o -> o.mapOrElse( + c -> run(context, c, args, name), + () -> CommandFeedback.of( + ShowFeedback.showText("Cannot show normal form; SDF3 was not configured in '" + args.root + "'", name)) + ), + // TODO: should we propagate configuration errors here? Every task that requires some configuration will + // propagate the same configuration errors, which would lead to duplicates. + e -> CommandFeedback.ofTryExtractMessagesFrom(e, args.root) + ); + } + + private CommandFeedback run(ExecContext context, Sdf3SpecConfig config, Sdf3ShowNormalForm.Args args, String name) { + return context.require(toNormalForm.createTask( + inputSupplier(desugar.createSupplier(parse.inputBuilder().withFile(args.file).buildRecoverableAstSupplier()), config.placeholders))) + .mapOrElse(ast -> provideOutput(context, args.concrete, ast, args.file), e -> CommandFeedback.ofTryExtractMessagesFrom(e, args.file)); } @Override public String getId() { diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecConfig.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecConfig.java index e48fa654b..c15440587 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecConfig.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecConfig.java @@ -1,11 +1,13 @@ package mb.sdf3.task.spec; +import mb.common.option.Option; import mb.common.util.ListView; import mb.pie.api.STask; import mb.resource.hierarchical.ResourcePath; import mb.resource.util.SeparatorUtil; import org.checkerframework.checker.nullness.qual.Nullable; import org.metaborg.sdf2table.parsetable.ParseTableConfiguration; +import org.metaborg.util.tuple.Tuple2; import java.io.Serializable; @@ -16,6 +18,7 @@ public class Sdf3SpecConfig implements Serializable { public final ListView includeDirectories; public final ListView> sourceFileOrigins; public final ParseTableConfiguration parseTableConfig; + public final Option> placeholders; public Sdf3SpecConfig( ResourcePath rootDirectory, @@ -23,14 +26,15 @@ public Sdf3SpecConfig( ResourcePath mainFile, ListView includeDirectories, ListView> sourceFileOrigins, - ParseTableConfiguration parseTableConfig - ) { + ParseTableConfiguration parseTableConfig, + Option> placeholders) { this.rootDirectory = rootDirectory; this.mainSourceDirectory = mainSourceDirectory; this.mainFile = mainFile; this.includeDirectories = includeDirectories; this.sourceFileOrigins = sourceFileOrigins; this.parseTableConfig = parseTableConfig; + this.placeholders = placeholders; } public String getMainModuleName() { @@ -41,7 +45,8 @@ public static Sdf3SpecConfig createDefault(ResourcePath rootDirectory, ListView< final ResourcePath mainSourceDirectory = rootDirectory.appendRelativePath("src"); final ResourcePath mainFile = mainSourceDirectory.appendRelativePath("start.sdf3"); final ParseTableConfiguration parseTableConfig = createDefaultParseTableConfiguration(); - return new Sdf3SpecConfig(rootDirectory, mainSourceDirectory, mainFile, includeDirectories, sourceFileOrigins, parseTableConfig); + return new Sdf3SpecConfig(rootDirectory, mainSourceDirectory, mainFile, includeDirectories, sourceFileOrigins, parseTableConfig, + Option.ofNone()); } public static Sdf3SpecConfig createDefault(ResourcePath rootDirectory, ListView includeDirectories) { @@ -65,7 +70,8 @@ public static ParseTableConfiguration createDefaultParseTableConfiguration() { if(!mainFile.equals(that.mainFile)) return false; if(!includeDirectories.equals(that.includeDirectories)) return false; if(!sourceFileOrigins.equals(that.sourceFileOrigins)) return false; - return parseTableConfig.equals(that.parseTableConfig); + if(!parseTableConfig.equals(that.parseTableConfig)) return false; + return placeholders.equals(that.placeholders); } @Override public int hashCode() { @@ -75,6 +81,7 @@ public static ParseTableConfiguration createDefaultParseTableConfiguration() { result = 31 * result + includeDirectories.hashCode(); result = 31 * result + sourceFileOrigins.hashCode(); result = 31 * result + parseTableConfig.hashCode(); + result = 31 * result + placeholders.hashCode(); return result; } @@ -86,6 +93,7 @@ public static ParseTableConfiguration createDefaultParseTableConfiguration() { ", includeDirectories=" + includeDirectories + ", sourceFileOrigins=" + sourceFileOrigins + ", parseTableConfig=" + parseTableConfig + + ", placeholders=" + placeholders + '}'; } } diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecToParseTable.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecToParseTable.java index 52d583c7c..375c29c8d 100644 --- a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecToParseTable.java +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/spec/Sdf3SpecToParseTable.java @@ -1,5 +1,6 @@ package mb.sdf3.task.spec; +import mb.common.option.Option; import mb.common.result.ExpectException; import mb.common.result.Result; import mb.common.util.ListView; @@ -19,10 +20,13 @@ import mb.sdf3.task.Sdf3ToPermissive; import mb.sdf3.task.spoofax.Sdf3GetSourceFilesWrapper; import mb.sdf3.task.spoofax.Sdf3ParseWrapper; +import mb.sdf3.task.util.Sdf3StrategoTransformTaskDef; + import org.checkerframework.checker.nullness.qual.Nullable; import org.metaborg.sdf2table.grammar.NormGrammar; import org.metaborg.sdf2table.io.NormGrammarReader; import org.metaborg.sdf2table.parsetable.ParseTable; +import org.metaborg.util.tuple.Tuple2; import org.spoofax.interpreter.terms.IStrategoTerm; import javax.inject.Inject; @@ -31,6 +35,8 @@ import java.util.ArrayList; import java.util.Set; +import static mb.sdf3.task.util.Sdf3StrategoTransformTaskDef.inputSupplier; + @Sdf3Scope public class Sdf3SpecToParseTable implements TaskDef> { public static class Input implements Serializable { @@ -109,12 +115,12 @@ public Input(Sdf3SpecConfig config, boolean createCompletionTable) { modulesAstSuppliers.add(parseInputBuilder.withFile(classLoaderResources.getDefinitionResource("permissive-water.sdf3").getPath()).buildAstSupplier()); try { - final IStrategoTerm mainNormalizedGrammar = context.require(toNormalized(mainModuleAstSupplier)) + final IStrategoTerm mainNormalizedGrammar = context.require(toNormalized(mainModuleAstSupplier, input.config.placeholders)) .expect(e -> new ExpectException("Transforming SDF3 grammar of main module " + mainModuleAstSupplier + " to normal form failed", e)); final NormGrammarReader normGrammarReader = new NormGrammarReader(); for(Supplier> astSupplier : modulesAstSuppliers) { - final IStrategoTerm normalizedGrammarTerm = context.require(toNormalized(astSupplier)) + final IStrategoTerm normalizedGrammarTerm = context.require(toNormalized(astSupplier, input.config.placeholders)) .expect(e -> new ExpectException("Transforming SDF3 grammar of " + astSupplier + " to normal form failed", e)); normGrammarReader.addModuleAst(normalizedGrammarTerm); } @@ -133,11 +139,11 @@ public Input(Sdf3SpecConfig config, boolean createCompletionTable) { // main module is the actual main module in case of creating a completion parse table. normGrammarReader.addModuleAst(mainNormalizedGrammar); - final IStrategoTerm mainCompletionNormalizedGrammar = context.require(toCompletionNormalized(mainModuleAstSupplier)) + final IStrategoTerm mainCompletionNormalizedGrammar = context.require(toCompletionNormalized(mainModuleAstSupplier, input.config.placeholders)) .expect(e -> new ExpectException("Transforming SDF3 grammar of main module " + mainModuleAstSupplier + " to completion normal form failed", e)); for(Supplier> astSupplier : modulesAstSuppliers) { - final IStrategoTerm normalizedGrammarTerm = context.require(toCompletionNormalized(astSupplier)) + final IStrategoTerm normalizedGrammarTerm = context.require(toCompletionNormalized(astSupplier, input.config.placeholders)) .expect(e -> new ExpectException("Transforming SDF3 grammar of " + astSupplier + " to completion normal form failed", e)); normGrammarReader.addModuleAst(normalizedGrammarTerm); } @@ -166,11 +172,14 @@ public Input(Sdf3SpecConfig config, boolean createCompletionTable) { return tags.isEmpty() || tags.contains(Interactivity.NonInteractive); } - private Task> toNormalized(Supplier> astSupplier) { - return toNormalForm.createTask(toPermissive.createSupplier(astSupplier)); + private Task> toNormalized(Supplier> astSupplier, + Option> placeholders) { + return toNormalForm.createTask( + inputSupplier(toPermissive.createSupplier(astSupplier), placeholders)); } - private Task> toCompletionNormalized(Supplier> astSupplier) { - return toNormalForm.createTask(toCompletion.createSupplier(astSupplier)); + private Task> toCompletionNormalized(Supplier> astSupplier, + Option> placeholders) { + return toNormalForm.createTask(inputSupplier(toCompletion.createSupplier(astSupplier), placeholders)); } } diff --git a/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/util/Sdf3StrategoTransformTaskDef.java b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/util/Sdf3StrategoTransformTaskDef.java new file mode 100644 index 000000000..5619dac59 --- /dev/null +++ b/lwb/metalang/sdf3/sdf3/src/main/java/mb/sdf3/task/util/Sdf3StrategoTransformTaskDef.java @@ -0,0 +1,101 @@ +package mb.sdf3.task.util; + +import java.io.Serializable; +import java.util.Objects; +import java.util.Set; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.metaborg.util.tuple.Tuple2; +import org.spoofax.interpreter.terms.IStrategoTerm; + +import mb.common.option.Option; +import mb.common.result.Result; +import mb.common.util.ListView; +import mb.pie.api.ExecContext; +import mb.pie.api.Supplier; +import mb.pie.api.ValueSupplier; +import mb.sdf3.stratego.Sdf3Context; +import mb.stratego.common.StrategoRuntime; +import mb.stratego.common.Strategy; +import mb.stratego.pie.GetStrategoRuntimeProvider; +import mb.stratego.pie.StrategoTransformTaskDef; + +public abstract class Sdf3StrategoTransformTaskDef extends StrategoTransformTaskDef { + public static class Input implements Serializable { + public final Supplier> astSupplier; + public final Option strategoQualifier; + public final Option> placeholders; + + public Input(Supplier> astSupplier, Option strategoQualifier, + Option> placeholders) { + this.astSupplier = astSupplier; + this.strategoQualifier = strategoQualifier; + this.placeholders = placeholders; + } + + @Override public boolean equals(@Nullable Object o) { + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; + final Sdf3StrategoTransformTaskDef.Input input = (Sdf3StrategoTransformTaskDef.Input) o; + return astSupplier.equals(input.astSupplier) && strategoQualifier.equals(input.strategoQualifier); + } + + @Override public int hashCode() { + return Objects.hash(astSupplier, strategoQualifier); + } + + @Override public String toString() { + return "Input{" + + "astSupplier=" + astSupplier + + ", strategoQualifier='" + strategoQualifier + '\'' + + '}'; + } + } + + public Sdf3StrategoTransformTaskDef(GetStrategoRuntimeProvider getStrategoRuntimeProvider, + ListView strategyNames) { + super(getStrategoRuntimeProvider, strategyNames); + } + + public Sdf3StrategoTransformTaskDef(GetStrategoRuntimeProvider getStrategoRuntimeProvider, + String... strategyNames) { + super(getStrategoRuntimeProvider, strategyNames); + } + + public Sdf3StrategoTransformTaskDef(GetStrategoRuntimeProvider getStrategoRuntimeProvider, Strategy... strategies) { + super(getStrategoRuntimeProvider, strategies); + } + + @Override protected StrategoRuntime getStrategoRuntime(ExecContext context, Input input) { + return super.getStrategoRuntime(context, input).addContextObject(new Sdf3Context(input.strategoQualifier, input.placeholders)); + } + + @Override protected Result getAst(ExecContext context, Input input) { + return input.astSupplier.get(context); + } + + @Override public boolean shouldExecWhenAffected(Supplier> input, Set tags) { + return shouldExecWhenAffected(tags); + } + + public boolean shouldExecWhenAffected(Set tags) { + return true; + } + + public static Supplier> inputSupplier(Supplier> astSupplier, String strategoQualifier, + Option> placeholders) { + return new ValueSupplier<>(Result.ofOk(new Input(astSupplier, Option.ofSome(strategoQualifier), placeholders))); + } + + public static Supplier> inputSupplier(Supplier> astSupplier, String strategoQualifier) { + return new ValueSupplier<>(Result.ofOk(new Input(astSupplier, Option.ofSome(strategoQualifier), Option.ofNone()))); + } + + public static Supplier> inputSupplier(Supplier> astSupplier, Option> placeholders) { + return new ValueSupplier<>(Result.ofOk(new Input(astSupplier, Option.ofNone(), placeholders))); + } + + public static Supplier> inputSupplier(Supplier> astSupplier) { + return new ValueSupplier<>(Result.ofOk(new Input(astSupplier, Option.ofNone(), Option.ofNone()))); + } +} diff --git a/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/TestBase.java b/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/TestBase.java index f1d12e3eb..8ce7b6d0b 100644 --- a/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/TestBase.java +++ b/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/TestBase.java @@ -1,5 +1,6 @@ package mb.sdf3.adapter; +import mb.common.option.Option; import mb.common.result.Result; import mb.common.util.ListView; import mb.log.dagger.DaggerLoggerComponent; @@ -69,7 +70,8 @@ protected TestBase() { Sdf3SpecConfig specConfig(ResourcePath rootDirectory, ResourcePath mainSourceDirectory, ResourcePath mainFile) { - return new Sdf3SpecConfig(rootDirectory, mainSourceDirectory, mainFile, ListView.of(), ListView.of(), Sdf3SpecConfig.createDefaultParseTableConfiguration()); + return new Sdf3SpecConfig(rootDirectory, mainSourceDirectory, mainFile, ListView.of(), ListView.of(), Sdf3SpecConfig.createDefaultParseTableConfiguration(), + Option.ofNone()); } Sdf3SpecConfig specConfig(ResourcePath rootDirectory) { diff --git a/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/ToNormalFormTest.java b/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/ToNormalFormTest.java index 545ec3550..f6bc7b0a7 100644 --- a/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/ToNormalFormTest.java +++ b/lwb/metalang/sdf3/sdf3/src/test/java/mb/sdf3/adapter/ToNormalFormTest.java @@ -1,12 +1,19 @@ package mb.sdf3.adapter; +import mb.common.option.Option; import mb.common.result.Result; import mb.pie.api.MixedSession; import mb.resource.text.TextResource; import mb.sdf3.task.Sdf3ToNormalForm; + import org.junit.jupiter.api.Test; +import org.metaborg.util.tuple.Tuple2; +import org.spoofax.interpreter.terms.IStrategoAppl; +import org.spoofax.interpreter.terms.IStrategoList; import org.spoofax.interpreter.terms.IStrategoTerm; +import org.spoofax.interpreter.terms.TermType; +import static mb.sdf3.task.util.Sdf3StrategoTransformTaskDef.inputSupplier; import static org.junit.jupiter.api.Assertions.*; import static org.spoofax.terms.util.TermUtils.*; @@ -15,7 +22,8 @@ class ToNormalFormTest extends TestBase { final TextResource resource = textResource("a.sdf3", "module nested/a context-free syntax A = "); final Sdf3ToNormalForm taskDef = component.getSdf3ToNormalForm(); try(final MixedSession session = newSession()) { - final Result result = session.require(taskDef.createTask(desugarSupplier(resource))); + final Result result = session.require(taskDef.createTask(inputSupplier(desugarSupplier(resource), + Option.ofSome(Tuple2.of("$", ""))))); assertTrue(result.isOk()); final IStrategoTerm output = result.unwrap(); log.info("{}", output); @@ -23,6 +31,20 @@ class ToNormalFormTest extends TestBase { assertTrue(isAppl(output, "Module")); assertTrue(isApplAt(output, 0, "Unparameterized")); assertTrue(isStringAt(output.getSubterm(0), 0, "normalized/nested/a-norm")); + + assertTrue(isListAt(output, 2)); + IStrategoList sections = toListAt(output, 2); + assertTrue(isApplAt(sections, 0, "SDFSection")); + IStrategoAppl section = toApplAt(sections, 0); + assertTrue(isApplAt(section, 0, "Kernel")); + IStrategoAppl kernel = toApplAt(section, 0); + assertTrue(isListAt(kernel, 0)); + IStrategoList productions = toListAt(kernel, 0); + assertTrue(isApplAt(productions, 1, "SdfProduction")); + IStrategoAppl production = toApplAt(productions, 1); + assertTrue(isApplAt(production, 0, "Lit")); + IStrategoAppl lit = toApplAt(production, 0); + assertTrue(isStringAt(lit, 0, "\"$A\"")); } } } diff --git a/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/sdf3/SpoofaxSdf3Configure.java b/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/sdf3/SpoofaxSdf3Configure.java index adb901d52..1f6e68f55 100644 --- a/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/sdf3/SpoofaxSdf3Configure.java +++ b/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/sdf3/SpoofaxSdf3Configure.java @@ -153,7 +153,8 @@ public Result configureSourceF mainFile.getPath(), ListView.copyOf(allIncludeDirectories), ListView.of(sourceFileOrigins), - parseTableConfiguration + parseTableConfiguration, + files.sdf3Placeholders() ); final SpoofaxSdf3Config.BuildParseTable mainBuildParseTable = new SpoofaxSdf3Config.BuildParseTable( mainSdf3SpecConfig, @@ -170,7 +171,8 @@ public Result configureSourceF extensionMainFile, // Replace main file mainSdf3SpecConfig.includeDirectories, mainSdf3SpecConfig.sourceFileOrigins, - mainSdf3SpecConfig.parseTableConfig + mainSdf3SpecConfig.parseTableConfig, + files.sdf3Placeholders() ); strategoConcreteSyntaxExtensions.add(sdf3SpecConfig); } diff --git a/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/stratego/SpoofaxStrategoConfigure.java b/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/stratego/SpoofaxStrategoConfigure.java index d1a987402..b5b255d18 100644 --- a/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/stratego/SpoofaxStrategoConfigure.java +++ b/lwb/spoofax.lwb.compiler/src/main/java/mb/spoofax/lwb/compiler/stratego/SpoofaxStrategoConfigure.java @@ -44,6 +44,7 @@ import org.metaborg.parsetable.IParseTable; import org.metaborg.sdf2table.parsetable.ParseTable; import org.metaborg.util.cmd.Arguments; +import org.metaborg.util.tuple.Tuple2; import org.spoofax.interpreter.terms.IStrategoTerm; import javax.inject.Inject; @@ -214,12 +215,13 @@ public Result toStrate // runtime, and injection explication (if enabled) module. final ResourcePath generatedSourcesDirectory = sourceFiles.generatedSourcesDirectory(); final String strategyAffix = sourceFiles.languageStrategyAffix(); + final Option> placeholders = sourceFiles.sdf3Placeholders(); try { spoofaxSdf3GenerationUtil.performSdf3GenerationIfEnabled(context, rootDirectory, new SpoofaxSdf3GenerationUtil.Callbacks() { @Override public void generateFromAst(ExecContext context, STask> astSupplier) throws SpoofaxStrategoConfigureException, InterruptedException { try { - sdf3ToPrettyPrinter(context, strategyAffix, generatedSourcesDirectory, astSupplier); + sdf3ToPrettyPrinter(context, strategyAffix, placeholders, generatedSourcesDirectory, astSupplier); } catch(RuntimeException | InterruptedException e) { throw e; // Do not wrap runtime and interrupted exceptions, rethrow them. } catch(Exception e) { @@ -227,7 +229,7 @@ public void generateFromAst(ExecContext context, STask> } // HACK: for now disabled completion runtime generation, as it is not used in Spoofax 3 (yet?) // try { -// sdf3ToCompletionRuntime(context, generatedSourcesDirectory, astSupplier); +// sdf3ToCompletionRuntime(context, placeholders, generatedSourcesDirectory, astSupplier); // } catch(RuntimeException | InterruptedException e) { // throw e; // Do not wrap runtime and interrupted exceptions, rethrow them. // } catch(Exception e) { @@ -344,10 +346,11 @@ private void sdf3ToSignature( private void sdf3ToPrettyPrinter( ExecContext context, String strategyAffix, + Option> placeholders, ResourcePath generatesSourcesDirectory, STask> astSupplier ) throws Exception { - final STask> supplier = sdf3ToPrettyPrinter.createSupplier(new Sdf3ToPrettyPrinter.Input(astSupplier, strategyAffix)); + final STask> supplier = sdf3ToPrettyPrinter.createSupplier(new Sdf3ToPrettyPrinter.Input(astSupplier, strategyAffix, placeholders)); spoofaxStrategoGenerationUtil.writePrettyPrintedFile(context, generatesSourcesDirectory, supplier); }