diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..7f348667c77aac1df614eddfa1721935c68dddcf --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/main/jastadd/mustache"] + path = src/main/jastadd/mustache + url = ../mustache.git diff --git a/build-template.gradle b/build-template.gradle new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/build.gradle b/build.gradle index aa3f23414c768afc27fada826a68fb8d66c2afaf..c9f5a8d654fdd6a213e364528ce70ebe1173e63c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,38 +1,58 @@ - -apply plugin: 'java' -apply plugin: 'jastadd' -apply plugin: 'application' -apply plugin: "idea" +plugins { + id 'java-library' + id 'org.jastadd' + id 'java' + id 'idea' + id 'java-test-fixtures' + id 'com.github.ben-manes.versions' version '0.34.0' +} sourceCompatibility = 1.8 - -mainClassName = 'org.jastadd.relast.compiler.RelastSourceToSourceCompiler' +targetCompatibility = 1.8 repositories { jcenter() } -buildscript { - repositories.jcenter() - dependencies { - classpath 'org.jastadd:jastaddgradle:1.13.3' +sourceSets { + model { + java { + srcDir "src/gen/java" + } } } -dependencies { - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.4.0' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.4.0' - testCompile 'org.assertj:assertj-core:3.12.1' - compile 'org.jastadd:jastadd:2.3.4' - runtime 'org.jastadd:jastadd:2.3.4' - compile group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' +task modelJar(type: Jar) { + group = "build" + archiveBaseName = 'model' + from sourceSets.model.output } -sourceSets { - main { - java.srcDir "src/gen/java" - java.srcDir "buildSrc/gen/java" - } +artifacts { + archives modelJar +} + +dependencies { + + modelImplementation group: 'org.jastadd', name: 'jastadd', version: '2.3.4' + modelImplementation group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' + + compileOnly files(modelJar.archiveFile.get()) + api group: 'org.jastadd', name: 'jastadd', version: '2.3.4' + api group: 'net.sf.beaver', name: 'beaver-rt', version: '0.9.11' + implementation group: 'com.github.jknack', name: 'handlebars', version: '4.2.0' + implementation group: 'org.yaml', name: 'snakeyaml', version: '1.27' + + // test + testRuntimeClasspath files(modelJar.archiveFile.get()) + + // test fixtures + testFixturesApi group: 'org.slf4j', name: 'slf4j-jdk14', version: '1.7.30' + testFixturesApi group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0' + testFixturesApi group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0' + testFixturesApi group: 'org.assertj', name: 'assertj-core', version: '3.18.0' + testFixturesApi group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.12.0-rc1' + testFixturesApi group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.12.0-rc1' } test { @@ -41,61 +61,47 @@ test { maxHeapSize = '1G' } -jar { - manifest { - attributes "Main-Class": 'org.jastadd.relast.compiler.RelastSourceToSourceCompiler' - } - - from { - configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } - } -} +// Input and output files for relast +def relastInputFiles = [ + "src/main/jastadd/RelAst.relast", + "src/main/jastadd/mustache/Mustache.relast" +] +def relastOutputFiles = [ + "src/gen/jastadd/RelAst.ast", + "src/gen/jastadd/RelAst.jadd", + "src/gen/jastadd/RelAstRefResolver.jadd", + "src/gen/jastadd/RelAstResolverStubs.jrag" +] task relast(type: JavaExec) { + classpath = files("libs/relast.jar") group = 'Build' - main = "-jar" doFirst { - delete "src/gen/jastadd/*.ast" - delete "src/gen/jastadd/RelAst.jadd" - delete "src/gen/jastadd/RelAstRefResolver.jadd" - delete "src/gen/jastadd/RelAstResolverStubs.jrag" - mkdir "src/gen/jastadd/" + delete relastOutputFiles + mkdir "src/gen/jastadd/" } args = [ - "libs/relast.jar", - "./src/main/jastadd/RelAst.relast", "--listClass=java.util.ArrayList", "--jastAddList=JastAddList", "--useJastAddNames", "--file", "--resolverHelper", "--grammarName=./src/gen/jastadd/RelAst" - ] - - inputs.files file("../libs/relast.jar"), - file("src/main/jastadd/RelAst.relast") - outputs.files file("./src/gen/jastadd/RelAst.ast"), - file("src/gen/jastadd/RelAst.jadd"), - file("src/gen/jastadd/RelAstRefResolver.jadd"), - file('src/gen/jastadd/RelAstResolverStubs.jrag') + ] + relastInputFiles + + inputs.files relastInputFiles + outputs.files relastOutputFiles } jastadd { configureModuleBuild() modules { //noinspection GroovyAssignabilityCheck - module("RelAst") { - - java { - basedir "." - include "src/main/**/*.java" - include "src/gen/**/*.java" - } + module("Preprocessor") { jastadd { - basedir "." include "src/main/jastadd/**/*.ast" include "src/main/jastadd/**/*.jadd" include "src/main/jastadd/**/*.jrag" @@ -105,10 +111,10 @@ jastadd { } scanner { - include "src/main/jastadd/scanner/Header.flex", [-4] - include "src/main/jastadd/scanner/Preamble.flex", [-3] - include "src/main/jastadd/scanner/Macros.flex", [-2] - include "src/main/jastadd/scanner/RulesPreamble.flex", [-1] + include "src/main/jastadd/scanner/Header.flex", [-4] + include "src/main/jastadd/scanner/Preamble.flex", [-3] + include "src/main/jastadd/scanner/Macros.flex", [-2] + include "src/main/jastadd/scanner/RulesPreamble.flex", [-1] include "src/main/jastadd/scanner/Keywords.flex", [0] include "src/main/jastadd/scanner/Symbols.flex", [1] include "src/main/jastadd/scanner/RulesPostamble.flex", [2] @@ -122,8 +128,8 @@ jastadd { } cleanGen.doFirst { - delete "src/gen/java/org" - delete "src/gen-res/BuildInfo.properties" + delete "src/gen" + delete "src/gen-res" } preprocessParser.doFirst { @@ -132,7 +138,7 @@ jastadd { } - module = "RelAst" + module = "Preprocessor" astPackage = 'org.jastadd.relast.ast' @@ -149,3 +155,11 @@ jastadd { } generateAst.dependsOn relast + +clean.dependsOn(cleanGen) + +modelJar.dependsOn(generateAst, modelClasses) +modelClasses.dependsOn(generateAst) +compileJava.dependsOn(modelJar) + +jar.dependsOn(modelJar) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1b16c34a71cf212ed0cfb883d14d1b8511903eb2..be52383ef49cdf484098989f96738b3d82d7810d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/libs/relast.jar b/libs/relast.jar index df0f6ce751cc1351525ff1d46cf09e83185adfe5..9f1d60c7c99a1e35d9cf5558d5f329c5aa7ba66e 100644 Binary files a/libs/relast.jar and b/libs/relast.jar differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000000000000000000000000000000000..5f99c5a83ca1b73205acb1de3960ccc501af1cc4 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,5 @@ +pluginManagement { + plugins { + id 'org.jastadd' version '1.13.3' + } +} diff --git a/src/main/jastadd/mustache b/src/main/jastadd/mustache new file mode 160000 index 0000000000000000000000000000000000000000..c10bed0d03e3fa18b8133ce1de48de7646899615 --- /dev/null +++ b/src/main/jastadd/mustache @@ -0,0 +1 @@ +Subproject commit c10bed0d03e3fa18b8133ce1de48de7646899615 diff --git a/src/main/java/org/jastadd/JastAddConfiguration.java b/src/main/java/org/jastadd/PreprocessorConfiguration.java similarity index 78% rename from src/main/java/org/jastadd/JastAddConfiguration.java rename to src/main/java/org/jastadd/PreprocessorConfiguration.java index 30774005356c0eb30354b7fb76e1bafe67d00873..726aba32937e914fd6d10e0af8f20d8372c35acd 100644 --- a/src/main/java/org/jastadd/JastAddConfiguration.java +++ b/src/main/java/org/jastadd/PreprocessorConfiguration.java @@ -32,56 +32,80 @@ import org.jastadd.option.Option; import java.io.PrintStream; import java.util.*; -import java.util.stream.Collectors; /** * Tracks JastAdd configuration options. * * @author Jesper Öqvist <jesper.oqvist@cs.lth.se> */ -public class JastAddConfiguration extends org.jastadd.Configuration { +public class PreprocessorConfiguration extends org.jastadd.Configuration { /** * Indicates if there were unknown command-line options */ final boolean unknownOptions; - - private boolean isJastAddCompliant; - - public boolean isJastAddCompliant() { - return isJastAddCompliant; - } - + private final Map<String, Option<?>> options = new HashMap<>(); + private final boolean isJastAddCompliant; + private final ArgumentParser argParser; /** * Parse options from an argument list. * * @param args Command-line arguments to build configuration from * @param err output stream to print configuration warnings to */ - public JastAddConfiguration(String[] args, PrintStream err, boolean isJastAddCompliant, Collection<Option<?>> extraOptions) { - ArgumentParser argParser = new ArgumentParser(); + public PreprocessorConfiguration(String[] args, PrintStream err, boolean isJastAddCompliant, Collection<Option<?>> extraOptions) { + argParser = new ArgumentParser(); this.isJastAddCompliant = isJastAddCompliant; + if (isJastAddCompliant) { Collection<Option<?>> jastAddOptions = allJastAddOptions(); + for (Option<?> o : jastAddOptions) { + options.put(o.name(), o); + } argParser.addOptions(jastAddOptions); + } - // if the JastAdd options are supported, we have to check for duplicates! - Set<String> jastAddOptionNames = jastAddOptions.stream().map(o -> o.name()).collect(Collectors.toSet()); - for (Option option : extraOptions) { - if (jastAddOptionNames.contains(option.name())) { - System.err.println("Unable to add option '" + option.name() + "', because there is a JastAdd option with the same name."); - } else { - argParser.addOption(option); - } + // if the JastAdd options are supported, we have to check for duplicates! + for (Option option : extraOptions) { + if (options.containsKey(option.name())) { + System.err.println("Unable to add option '" + option.name() + "', because there is a JastAdd option with the same name."); + } else { + argParser.addOption(option); + options.put(option.name(), option); } - } else { - argParser.addOptions(extraOptions); } unknownOptions = !argParser.parseArgs(args, err); filenames = argParser.getFilenames(); } + public ArgumentParser getArgParser() { + return argParser; + } + + public Optional<Option> getOption(String name) { + return options.containsKey(name) ? Optional.of(options.get(name)) : Optional.empty(); + } + + public boolean isJastAddCompliant() { + return isJastAddCompliant; + } + + /** + * Print help + * + * @param out Output stream to print help to. + */ + @Override + public void printHelp(PrintStream out) { + out.println("This program reads a number of .jrag, .jadd, and .ast files"); + out.println("Options:"); + argParser.printHelp(out); + out.println(); + out.println("Arguments:"); + out.println(" Names of abstract grammr (.ast) and aspect (.jrag and .jadd) files."); + } + /** * @return all files */ diff --git a/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java b/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java index adc9bde23565b83df6fe6885d6a5d92d3bbad8b3..7c136853f1dd1b7f33c134ac2425e320b1ff685c 100644 --- a/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java +++ b/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java @@ -1,6 +1,7 @@ package org.jastadd.relast.compiler; -import org.jastadd.JastAddConfiguration; +import org.jastadd.PreprocessorConfiguration; +import org.jastadd.option.FlagOption; import org.jastadd.option.Option; import java.util.ArrayList; @@ -8,20 +9,16 @@ import java.util.ArrayList; public abstract class AbstractCompiler { private final boolean jastAddCompliant; - protected ArrayList<Option<?>> options; private final String name; - - private JastAddConfiguration configuration; + protected ArrayList<Option<?>> options; + private PreprocessorConfiguration configuration; public AbstractCompiler(String name, boolean jastaddCompliant) { this.name = name; this.jastAddCompliant = jastaddCompliant; } - public JastAddConfiguration getConfiguration() throws CompilerException { - if (configuration == null) { - throw new CompilerException("Configuration only supported for JastAdd-compliant compilers!"); - } + public PreprocessorConfiguration getConfiguration() { return configuration; } @@ -29,7 +26,12 @@ public abstract class AbstractCompiler { options = new ArrayList<>(); initOptions(); - configuration = new JastAddConfiguration(args, System.err, jastAddCompliant, options); + configuration = new PreprocessorConfiguration(args, System.err, jastAddCompliant, options); + + if (configuration.shouldPrintHelp()) { + configuration.printHelp(System.out); + return 0; + } return compile(); } @@ -37,10 +39,13 @@ public abstract class AbstractCompiler { protected abstract int compile() throws CompilerException; protected void initOptions() { - // there are no options by default + if (!jastAddCompliant) { + addOption(new FlagOption("version", "print version info")); + addOption(new FlagOption("help", "print command-line usage info")); + } } - protected <OptionType extends Option<?>> OptionType addOption(OptionType option) { + protected <O extends Option<?>> O addOption(O option) { options.add(option); return option; } diff --git a/src/main/java/org/jastadd/relast/compiler/Mustache.java b/src/main/java/org/jastadd/relast/compiler/Mustache.java new file mode 100644 index 0000000000000000000000000000000000000000..3b428abf1f60b87ff004b7cb92aaefcbf99b03df --- /dev/null +++ b/src/main/java/org/jastadd/relast/compiler/Mustache.java @@ -0,0 +1,32 @@ +package org.jastadd.relast.compiler; + +import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.Template; +import com.github.jknack.handlebars.io.ClassPathTemplateLoader; +import com.github.jknack.handlebars.io.TemplateLoader; +import org.yaml.snakeyaml.Yaml; + +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; + +public class Mustache { + + public static void javaMustache(String templateFileName, String yamlFileName, String outputFileName) throws IOException { + + Object context = new Yaml().load(new FileReader(yamlFileName)); + TemplateLoader loader = new ClassPathTemplateLoader(); + loader.setSuffix(".mustache"); // the default is ".hbs" + + Handlebars handlebars = new Handlebars(loader); + handlebars.prettyPrint(true); // set handlebars to mustache mode (skip some whitespace) + Template template = handlebars.compile(templateFileName); + + try (Writer w = new FileWriter(outputFileName)) { + template.apply(context, w); + w.flush(); + } + } + +} diff --git a/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java b/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..b5624f2aee6e22179800de5e09c471df8b3e2071 --- /dev/null +++ b/src/main/java/org/jastadd/relast/compiler/RelAstProcessor.java @@ -0,0 +1,133 @@ +package org.jastadd.relast.compiler; + +import org.jastadd.option.ValueOption; +import org.jastadd.relast.ast.GrammarFile; +import org.jastadd.relast.ast.Program; +import org.jastadd.relast.parser.RelAstParser; +import org.jastadd.relast.scanner.RelAstScanner; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; + +public abstract class RelAstProcessor extends AbstractCompiler { + + protected ValueOption optionOutputBaseDir; + protected ValueOption optionInputBaseDir; + + public RelAstProcessor(String name, boolean jastAddCompliant) { + super(name, jastAddCompliant); + } + + protected static boolean isGrammarFile(String fileName) { + String extension = fileName.subSequence(fileName.lastIndexOf('.'), fileName.length()).toString(); + return extension.equals(".relast") || extension.equals(".ast"); + } + + @Override + protected void initOptions() { + optionOutputBaseDir = addOption(new ValueOption("outputBaseDir", "base directory for generated files")); + optionInputBaseDir = addOption(new ValueOption("inputBaseDir", "base directory for input files")); + super.initOptions(); + } + + @Override + protected int compile() throws CompilerException { + final Path inputBasePath; + if (optionInputBaseDir.isMatched()) { + inputBasePath = Paths.get(optionInputBaseDir.value()).toAbsolutePath(); + } else { + inputBasePath = Paths.get(".").toAbsolutePath(); + printMessage("No input base dir is set. Assuming current directory '" + inputBasePath.toAbsolutePath().toString() + "'."); + } + + if (!inputBasePath.toFile().exists()) { + printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' does not exist. Exiting..."); + System.exit(-1); + } else if (!inputBasePath.toFile().isDirectory()) { + printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' is not a directory. Exiting..."); + System.exit(-1); + } + + final Path outputBasePath; + if (optionOutputBaseDir.isMatched()) { + outputBasePath = Paths.get(optionOutputBaseDir.value()).toAbsolutePath(); + } else { + throw new CompilerException("No output base dir is set."); + } + + if (outputBasePath.toFile().exists() && !outputBasePath.toFile().isDirectory()) { + printMessage("Output path '" + inputBasePath.toAbsolutePath().toString() + "' exists, but is not a directory. Exiting..."); + } + + printMessage("Running " + getName()); + + // gather all files + Collection<Path> inputFiles = new ArrayList<>(); + getConfiguration().getFiles().forEach(name -> relativizeFileName(inputBasePath, Paths.get(name)).ifPresent(inputFiles::add)); + + + Program program = parseProgram(inputFiles); + + return processGrammar(program, inputBasePath, outputBasePath); + } + + protected abstract int processGrammar(Program program, Path inputBasePath, Path outputBasePath) throws CompilerException; + + private Optional<Path> relativizeFileName(Path inputBasePath, Path filePath) { + if (filePath.isAbsolute()) { + if (filePath.startsWith(inputBasePath)) { + return Optional.of(filePath.relativize(inputBasePath)); + } else { + printMessage("Path '" + filePath + "' is not contained in the base path '" + inputBasePath + "'."); + return Optional.empty(); + } + } else { + return Optional.of(inputBasePath.resolve(filePath)); + } + } + + protected void printMessage(String message) { + System.out.println(message); + } + + protected void writeToFile(Path path, String str) throws CompilerException { + //noinspection ResultOfMethodCallIgnored + path.getParent().toFile().mkdirs(); // create directory structure if necessary + try (PrintWriter writer = new PrintWriter(path.toFile())) { + writer.print(str); + } catch (Exception e) { + throw new CompilerException("Could not write to file " + path, e); + } + } + + private Program parseProgram(Collection<Path> inputFiles) { + Program program = new Program(); + + RelAstParser parser = new RelAstParser(); + + inputFiles.stream().filter(path -> isGrammarFile(path.toString())).forEach( + path -> { + try (BufferedReader reader = Files.newBufferedReader(path)) { + RelAstScanner scanner = new RelAstScanner(reader); + GrammarFile inputGrammar = (GrammarFile) parser.parse(scanner); + inputGrammar.setFileName(path.toString()); + program.addGrammarFile(inputGrammar); + inputGrammar.treeResolveAll(); + } catch (IOException | beaver.Parser.Exception e) { + printMessage("Could not parse grammar file " + path); + e.printStackTrace(); + } + } + ); + + return program; + } +} + diff --git a/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java index 7d6949f5ff7ec74d2e1963e564bff9d633d4fbec..e919ba822c44bee59b935bbc8d7e553c571101c9 100644 --- a/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java +++ b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java @@ -1,28 +1,14 @@ package org.jastadd.relast.compiler; -import beaver.Parser; -import org.jastadd.option.ValueOption; import org.jastadd.relast.ast.GrammarFile; import org.jastadd.relast.ast.Program; -import org.jastadd.relast.parser.RelAstParser; -import org.jastadd.relast.scanner.RelAstScanner; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.PrintWriter; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Optional; -public class RelastSourceToSourceCompiler extends AbstractCompiler { +public class RelastSourceToSourceCompiler extends RelAstProcessor { - protected ValueOption optionOutputBaseDir; - protected ValueOption optionInputBaseDir; - public RelastSourceToSourceCompiler(String name, boolean jastAddCompliant) { super(name, jastAddCompliant); } @@ -42,103 +28,16 @@ public class RelastSourceToSourceCompiler extends AbstractCompiler { } @Override - protected void initOptions() { - optionOutputBaseDir = addOption(new ValueOption("outputBaseDir", "base directory for generated files")); - optionInputBaseDir = addOption(new ValueOption("inputBaseDir", "base directory for input files")); - } - - @Override - protected int compile() throws CompilerException { - final Path inputBasePath; - if (optionInputBaseDir.isMatched()) { - inputBasePath = Paths.get(optionInputBaseDir.value()).toAbsolutePath(); - } else { - inputBasePath = Paths.get(".").toAbsolutePath(); - printMessage("No input base dir is set. Assuming current directory '" + inputBasePath.toAbsolutePath().toString() + "'."); - } - - if (!inputBasePath.toFile().exists()) { - printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' does not exist. Exiting..."); - System.exit(-1); - } else if (!inputBasePath.toFile().isDirectory()) { - printMessage("Input path '" + inputBasePath.toAbsolutePath().toString() + "' is not a directory. Exiting..."); - System.exit(-1); - } - - final Path outputBasePath; - if (optionOutputBaseDir.isMatched()) { - outputBasePath = Paths.get(optionOutputBaseDir.value()).toAbsolutePath(); - } else { - throw new CompilerException("No output base dir is set."); - } - - if (outputBasePath.toFile().exists() && !outputBasePath.toFile().isDirectory()) { - printMessage("Output path '" + inputBasePath.toAbsolutePath().toString() + "' exists, but is not a directory. Exiting..."); - } - - printMessage("Running RelAST Preprocessor"); - - // gather all files - Collection<Path> inputFiles = new ArrayList<>(); - getConfiguration().getFiles().forEach(name -> relativizeFileName(inputBasePath, Paths.get(name)).ifPresent(path -> inputFiles.add(path))); - - - Program program = parseProgram(inputFiles); + protected int processGrammar(Program program, Path inputBasePath, Path outputBasePath) throws CompilerException { printMessage("Writing output files"); for (GrammarFile grammarFile : program.getGrammarFileList()) { + printMessage("Writing output file " + grammarFile.getFileName()); // TODO decide and document what the file name should be, the full path or a simple name? writeToFile(outputBasePath.resolve(inputBasePath.relativize(Paths.get(grammarFile.getFileName()))), grammarFile.generateAbstractGrammar()); } return 0; } - - private Optional<Path> relativizeFileName(Path inputBasePath, Path filePath) { - if (filePath.isAbsolute()) { - if (filePath.startsWith(inputBasePath)) { - return Optional.of(filePath.relativize(inputBasePath)); - } else { - printMessage("Path '" + filePath + "' is not contained in the base path '" + inputBasePath + "'."); - return Optional.empty(); - } - } else { - return Optional.of(inputBasePath.resolve(filePath)); - } - } - - private void printMessage(String message) { - System.out.println(message); - } - - private void writeToFile(Path path, String str) throws CompilerException { - try (PrintWriter writer = new PrintWriter(path.toFile())) { - writer.print(str); - } catch (Exception e) { - throw new CompilerException("Could not write to file " + path, e); - } - } - - private Program parseProgram(Collection<Path> inputFiles) throws CompilerException { - Program program = new Program(); - - RelAstParser parser = new RelAstParser(); - inputFiles.stream().filter(path -> isGrammarFile(path.toString())).forEach( - path -> { - try (BufferedReader reader = Files.newBufferedReader(path)) { - RelAstScanner scanner = new RelAstScanner(reader); - GrammarFile inputGrammar = (GrammarFile) parser.parse(scanner); - inputGrammar.setFileName(path.toString()); - program.addGrammarFile(inputGrammar); - inputGrammar.treeResolveAll(); - } catch (IOException | Parser.Exception e) { - printMessage("Could not parse grammar file " + path); - e.printStackTrace(); - } - } - ); - - return program; -} } diff --git a/src/main/java/org/jastadd/relast/compiler/Utils.java b/src/main/java/org/jastadd/relast/compiler/Utils.java deleted file mode 100644 index 8e2ef0c8bcb8266fe5789b2608f4a6f78c47548a..0000000000000000000000000000000000000000 --- a/src/main/java/org/jastadd/relast/compiler/Utils.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.jastadd.relast.compiler; - -import java.util.*; -import java.util.function.Predicate; - -import static java.util.stream.Collectors.toList; - -public class Utils { - public static <T> List<T> filterToList(Collection<T> collection, Predicate<T> predicate) { - return collection.stream().filter(predicate).collect(toList()); - } - - public static <T> Set<T> asSet(T... t) { - return new HashSet<T>(Arrays.asList(t)); - } -} diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml deleted file mode 100644 index 98cfd73c75df58d8598521bc10b043e214ec4ad8..0000000000000000000000000000000000000000 --- a/src/main/resources/log4j2.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<Configuration status="INFO"> - <Appenders> - <Console name="Console" target="SYSTEM_OUT"> - <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> - </Console> - </Appenders> - <Loggers> - <Root level="info"> - <AppenderRef ref="Console"/> - </Root> - </Loggers> -</Configuration> \ No newline at end of file diff --git a/src/test/java/org/jastadd/relast/tests/PreprocessorTest.java b/src/test/java/org/jastadd/relast/tests/PreprocessorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..c6fde6f5331ebc9e94680dee87b54ce31e58bad6 --- /dev/null +++ b/src/test/java/org/jastadd/relast/tests/PreprocessorTest.java @@ -0,0 +1,15 @@ +package org.jastadd.relast.tests; + +import org.jastadd.relast.compiler.RelastSourceToSourceCompiler; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Paths; + +public class PreprocessorTest extends RelAstProcessorTestBase { + + @Test + void testMinimalGrammar() throws IOException, InterruptedException { + directoryTest(RelastSourceToSourceCompiler.class, Paths.get("src/test/resources/MinimalGrammar")); + } +} diff --git a/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java b/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java deleted file mode 100644 index c87e51f7dbc70644b621998548d4f942160e0ea9..0000000000000000000000000000000000000000 --- a/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.jastadd.ros2rag.tests; - -import org.jastadd.relast.compiler.CompilerException; -import org.jastadd.relast.compiler.RelastSourceToSourceCompiler; -import org.junit.jupiter.api.Test; - -import java.io.File; -import java.nio.file.Paths; -import java.util.Objects; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class RelAstTest { - - void transform(boolean jastAddCompliant, String inputDir, String outputDir) throws CompilerException { - - System.out.println("Running test in directory '" + Paths.get(".").toAbsolutePath() + "'."); - assertTrue(Paths.get(inputDir).toFile().exists(), "input directory does not exist"); - assertTrue(Paths.get(inputDir).toFile().isDirectory(), "input directory is not a directory"); - - File outputDirFile = Paths.get(outputDir).toFile(); - if (outputDirFile.exists()) { - assertTrue(outputDirFile.isDirectory()); - if (Objects.requireNonNull(outputDirFile.list(), "Could not read output directory").length != 0) { - System.out.println("output directory is not empty!"); - } - } else { - assertTrue(outputDirFile.mkdir()); - } - - String[] args = { - "--outputBaseDir=" + outputDir, - "--inputBaseDir=" + inputDir, - "Example.relast" - }; - - new RelastSourceToSourceCompiler("testCompiler", jastAddCompliant).run(args); - } - - @Test - void transformMinimalExample() throws CompilerException { - transform(false,"src/test/resources/in", "src/test/resources/out-simple"); - transform(true,"src/test/resources/in", "src/test/resources/out-compliant"); - } -} diff --git a/src/test/resources/MinimalGrammar/config.yaml b/src/test/resources/MinimalGrammar/config.yaml new file mode 100644 index 0000000000000000000000000000000000000000..c22d8d533545dff1d7139fa62a5464506d703c13 --- /dev/null +++ b/src/test/resources/MinimalGrammar/config.yaml @@ -0,0 +1,10 @@ +- name: "MinimalGrammar" + args: + - "--inputBaseDir=in" + - "--outputBaseDir=out" + - "Example.relast" +- name: "Comments in front" + args: + - "--inputBaseDir=in" + - "--outputBaseDir=out" + - "CommentInFront.relast" diff --git a/src/test/resources/MinimalGrammar/in/CommentInFront.relast b/src/test/resources/MinimalGrammar/in/CommentInFront.relast new file mode 100644 index 0000000000000000000000000000000000000000..99b677ef4d8481b99e4d9f2f366764e1de6a568b --- /dev/null +++ b/src/test/resources/MinimalGrammar/in/CommentInFront.relast @@ -0,0 +1,2 @@ +// comment +CommentInFront ; diff --git a/src/test/resources/MinimalGrammar/in/Example.relast b/src/test/resources/MinimalGrammar/in/Example.relast new file mode 100644 index 0000000000000000000000000000000000000000..0d9c9069419cd5b38fd1f861e9152cce7ffeb750 --- /dev/null +++ b/src/test/resources/MinimalGrammar/in/Example.relast @@ -0,0 +1,13 @@ +Model ::= RobotArm ZoneModel; + +ZoneModel ::= <Size:IntPosition> SafetyZone:Zone*; + +Zone ::= Coordinate*; + +RobotArm ::= Joint* EndEffector <_AttributeTestSource:int> /<_AppropriateSpeed:double>/ ; // normally this would be: <AttributeTestSource:int> ; + +Joint ::= <Name> <CurrentPosition:IntPosition>; // normally this would be: <CurrentPosition:IntPosition> + +EndEffector : Joint; + +Coordinate ::= <Position:IntPosition>; diff --git a/src/test/resources/in/Example.relast b/src/test/resources/in/Example.relast deleted file mode 100644 index aa17ad814128cf155d844e0f36f1114b1b1afa33..0000000000000000000000000000000000000000 --- a/src/test/resources/in/Example.relast +++ /dev/null @@ -1,13 +0,0 @@ -Model ::= RobotArm ZoneModel ; - -ZoneModel ::= <Size:IntPosition> SafetyZone:Zone* ; - -Zone ::= Coordinate* ; - -RobotArm ::= Joint* EndEffector <_AttributeTestSource:int> /<_AppropriateSpeed:double>/ ; // normally this would be: <AttributeTestSource:int> ; - -Joint ::= <Name> <CurrentPosition:IntPosition> ; // normally this would be: <CurrentPosition:IntPosition> - -EndEffector : Joint; - -Coordinate ::= <Position:IntPosition> ; diff --git a/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java b/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java new file mode 100644 index 0000000000000000000000000000000000000000..bdd620c19de7ae3d9891960c6191c51cfb6ef37d --- /dev/null +++ b/src/testFixtures/java/org/jastadd/relast/tests/RelAstProcessorTestBase.java @@ -0,0 +1,141 @@ +package org.jastadd.relast.tests; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import org.assertj.core.util.Files; +import org.jastadd.relast.tests.config.Configuration; +import org.junit.jupiter.api.Assertions; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class RelAstProcessorTestBase { + + protected static int runProcess(File workingDirectory, List<String> command, StringBuilder outStringBuider, StringBuilder errStringBuilder) throws IOException, InterruptedException { + + File outFile = Files.newTemporaryFile(); + File errFile = Files.newTemporaryFile(); + + ProcessBuilder pb = new ProcessBuilder(command). + directory(workingDirectory) + .redirectOutput(outFile) + .redirectError(errFile); + + Process p = pb.start(); + try { + p.waitFor(); + } catch (InterruptedException e) { + if (Thread.interrupted()) // Clears interrupted status! + throw e; + } + + try (BufferedReader outReader = new BufferedReader(new FileReader(outFile))) { + String outLine; + while ((outLine = outReader.readLine()) != null) { + outStringBuider.append(outLine).append("\n"); + } + } + + try (BufferedReader errReader = new BufferedReader(new FileReader(errFile))) { + String errLine; + while ((errLine = errReader.readLine()) != null) { + errStringBuilder.append(errLine).append("\n"); + } + } + + return p.exitValue(); + } + + protected static int runJavaProcess(Class<?> klass, File workingDirectory, List<String> args, StringBuilder outStringBuider, StringBuilder errStringBuilder) throws IOException, InterruptedException { + String javaHome = System.getProperty("java.home"); + String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; + String classpath = System.getProperty("java.class.path"); + String className = klass.getName(); + + List<String> command = new LinkedList<>(); + command.add(javaBin); + command.add("-cp"); + command.add(classpath); + command.add(className); + if (args != null) { + command.addAll(args); + } + + System.out.println("Running java -jar -cp [...] " + className + " " + args.stream().reduce((s1, s2) -> s1 + " " + s2).orElse("")); + + return runProcess(workingDirectory, command, outStringBuider, errStringBuilder); + } + + protected void directoryTest(Class<?> mainClass, Path dir) throws IOException, InterruptedException { + dir = dir.toAbsolutePath(); + Path configFile = dir.resolve("config.yaml"); + ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); + List<Configuration> configs = mapper.readValue(configFile.toFile(), new TypeReference<List<Configuration>>() { + }); + + for (Configuration config : configs) { + + StringBuilder outBuilder = new StringBuilder(); + StringBuilder errBuilder = new StringBuilder(); + int returnValue = runJavaProcess(mainClass, dir.toFile(), Arrays.asList(config.getArgs()), outBuilder, errBuilder); + String out = outBuilder.toString(); + String err = errBuilder.toString(); + + System.out.println(out); + System.err.println(err); + + if (config.shouldFail()) { + Assertions.assertNotEquals(0, returnValue, "Zero return value of preprocessor for negative test."); + } else { + Assertions.assertEquals(0, returnValue, "Non-Zero return value of preprocessor for positive test."); + } + + for (String errMatchString : config.getErrMatches()) { + if (!err.matches(errMatchString)) { + Assertions.fail("Error stream does not match '" + errMatchString + "'"); + } + } + + for (String errContainsString : config.getErrContains()) { + if (!err.contains(errContainsString)) { + Assertions.fail("Error stream does not contain '" + errContainsString + "'"); + } + } + + for (String outMatchString : config.getOutMatches()) { + if (!out.matches(outMatchString)) { + Assertions.fail("Error stream does not match '" + outMatchString + "'"); + } + } + + for (String outContainsString : config.getOutContains()) { + if (!out.contains(outContainsString)) { + Assertions.fail("Error stream does not contain '" + outContainsString + "'"); + } + } + } + } + + protected void ensureOutputDir(String outputDir) { + File outputDirFile = Paths.get(outputDir).toFile(); + if (outputDirFile.exists()) { + assertTrue(outputDirFile.isDirectory()); + if (Objects.requireNonNull(outputDirFile.list(), "Could not read output directory").length != 0) { + System.out.println("output directory is not empty!"); + } + } else { + assertTrue(outputDirFile.mkdirs()); + } + } +} diff --git a/src/testFixtures/java/org/jastadd/relast/tests/config/Configuration.java b/src/testFixtures/java/org/jastadd/relast/tests/config/Configuration.java new file mode 100644 index 0000000000000000000000000000000000000000..7a1ec9419bc5bb39463941ff70ceb097ab33c5d5 --- /dev/null +++ b/src/testFixtures/java/org/jastadd/relast/tests/config/Configuration.java @@ -0,0 +1,78 @@ +package org.jastadd.relast.tests.config; + +public class Configuration { + + private String name; + private String[] args = new String[]{}; + private boolean fail = false; + private String[] errMatches = new String[]{}; + private String[] errContains = new String[]{}; + private String[] outMatches = new String[]{}; + private String[] outContains = new String[]{}; + + @com.fasterxml.jackson.annotation.JsonGetter("out-matches") + public String[] getOutMatches() { + return outMatches; + } + + @com.fasterxml.jackson.annotation.JsonSetter("out-matches") + public void setOutMatches(String[] outMatches) { + this.outMatches = outMatches; + } + + @com.fasterxml.jackson.annotation.JsonGetter("out-contains") + public String[] getOutContains() { + return outContains; + } + + @com.fasterxml.jackson.annotation.JsonSetter("out-contains") + public void setOutContains(String[] outContains) { + this.outContains = outContains; + } + + @com.fasterxml.jackson.annotation.JsonGetter("err-matches") + public String[] getErrMatches() { + return errMatches; + } + + @com.fasterxml.jackson.annotation.JsonSetter("err-matches") + public void setErrMatches(String[] errMatches) { + this.errMatches = errMatches; + } + + @com.fasterxml.jackson.annotation.JsonGetter("err-contains") + public String[] getErrContains() { + return errContains; + } + + @com.fasterxml.jackson.annotation.JsonSetter("err-contains") + public void setErrContains(String[] errMatches) { + this.errContains = errContains; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String[] getArgs() { + return args; + } + + public void setArgs(String[] args) { + this.args = args; + } + + @com.fasterxml.jackson.annotation.JsonGetter("fail") + public boolean shouldFail() { + return fail; + } + + @com.fasterxml.jackson.annotation.JsonSetter("fail") + public void shouldFail(boolean fail) { + this.fail = fail; + } +}