......@@ -28,60 +28,90 @@
package org.jastadd;
import org.jastadd.option.ArgumentParser;
import org.jastadd.option.FlagOption;
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 {
if (option.name().equals("help") && option instanceof FlagOption) {
this.helpOption = (FlagOption) option;
} else if (option.name().equals("version") && option instanceof FlagOption) {
this.versionOption = (FlagOption) option;
}
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
*/
......@@ -110,7 +140,6 @@ public class JastAddConfiguration extends org.jastadd.Configuration {
allOptions.add(cacheCycleOption);
allOptions.add(componentCheckOption);
allOptions.add(inhEqCheckOption);
allOptions.add(suppressWarningsOption);
allOptions.add(refineLegacyOption);
allOptions.add(licenseOption);
allOptions.add(debugOption);
......@@ -150,27 +179,6 @@ public class JastAddConfiguration extends org.jastadd.Configuration {
// New since 2.3.4
allOptions.add(optimizeImports);
// Deprecated in 2.1.5.
allOptions.add(doxygenOption);
allOptions.add(cacheAllOption);
allOptions.add(noCachingOption);
allOptions.add(cacheNoneOption);
allOptions.add(cacheImplicitOption);
allOptions.add(ignoreLazyOption);
allOptions.add(fullFlushOption);
// Deprecated in 2.1.9.
allOptions.add(docOption);
allOptions.add(java1_4Option); // Disabled in 2.1.10.
allOptions.add(noLazyMapsOption);
allOptions.add(noVisitCheckOption);
allOptions.add(noCacheCycleOption);
allOptions.add(noRefineLegacyOption);
allOptions.add(noComponentCheckOption);
allOptions.add(noInhEqCheckOption);
allOptions.add(noStaticOption);
allOptions.add(deterministicOption);
return allOptions;
}
......
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;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
public abstract class AbstractCompiler {
private final boolean jastAddCompliant;
protected ArrayList<Option<?>> options;
private final String name;
protected ArrayList<Option<?>> options;
private PreprocessorConfiguration configuration;
private JastAddConfiguration configuration;
public AbstractCompiler(String name, boolean jastaddCompliant) {
protected 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 +28,22 @@ 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;
}
if (configuration.shouldPrintVersion()) {
try {
ResourceBundle resources = ResourceBundle.getBundle("preprocessor");
System.out.println(getName() + ", version " + resources.getString("version"));
} catch (MissingResourceException e) {
System.out.println(getName() + ", unknown version");
}
return 0;
}
return compile();
}
......@@ -37,10 +51,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;
}
......
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.*;
import java.nio.file.Paths;
public class Mustache {
private Mustache() {
// hide public constructor
}
public static void javaMustache(String templateFileName, File yamlFile, String outputFileName) throws IOException {
//noinspection ResultOfMethodCallIgnored
Paths.get(outputFileName).getParent().toFile().mkdirs(); // create directory structure if necessary
Object context = new Yaml().load(new FileReader(yamlFile));
applyTemplate(templateFileName, outputFileName, context);
}
public static void javaMustache(String templateFileName, String yaml, String outputFileName) throws IOException {
//noinspection ResultOfMethodCallIgnored
Paths.get(outputFileName).getParent().toFile().mkdirs(); // create directory structure if necessary
Object context = new Yaml().load(new StringReader(yaml));
applyTemplate(templateFileName, outputFileName, context);
}
private static void applyTemplate(String templateFileName, String outputFileName, Object context) throws IOException {
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)
handlebars.infiniteLoops(true); // allow partial recursion
Template template = handlebars.compile(templateFileName);
try (Writer w = new FileWriter(outputFileName)) {
template.apply(context, w);
w.flush();
}
}
}
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;
protected RelAstProcessor(String name, boolean jastAddCompliant) {
super(name, jastAddCompliant);
}
protected 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);
} catch (IOException | beaver.Parser.Exception e) {
printMessage("Could not parse grammar file " + path);
e.printStackTrace();
}
}
);
program.treeResolveAll();
return program;
}
}
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);
}
public static void main(String[] args) {
try {
new RelastSourceToSourceCompiler("relast-preprocessor", true).run(args);
new RelastSourceToSourceCompiler("Relational RAGs Source-To-Source Compiler", false).run(args);
} catch (CompilerException e) {
System.err.println(e.getMessage());
System.exit(-1);
}
}
protected static boolean isGrammarFile(String fileName) {
@Override
protected 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"));
}
@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;
}
}
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));
}
}
<?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
package org.jastadd.relast.tests;
import org.jastadd.relast.compiler.RelastSourceToSourceCompiler;
import org.junit.jupiter.api.BeforeAll;
public class PreprocessorTest extends RelAstProcessorTestBase {
@BeforeAll
static void init() {
mainClass = RelastSourceToSourceCompiler.class;
}
}
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");
}
}
- name: "Parse and reprint tests"
args:
- "--inputBaseDir=in"
- "--outputBaseDir=out"
- "CommentsA.relast"
out: "out"
expected: "in"
compare: true
// this file only contains comments
//
//
// like this one
/* or this one */
/* or this
one */
/**/
//test
/*test*/
//
- name: "dependencies between relast files"
args:
- "--inputBaseDir=in"
- "--outputBaseDir=out"
- "Rel.relast"
- "A.relast"
- "B.relast"
- "C.relast"
out: "out"
expected: "in"
compare: true
C ::= [C1] [C2];
C1 ::= A;
C2 ::= B;
rel A.b? -> B;
rel B.c? -> C;
rel C.a? -> A;
- name: "Parse and reprint tests"
args:
- "--inputBaseDir=in"
- "--outputBaseDir=out"
- "Example.relast"
- "CommentInFront.relast"
- "DefaultTypeOfToken.ast"
out: "out"
expected: "in"
compare: true