diff --git a/.gitignore b/.gitignore index 87b4cdd3d7c6a41502ca98703abeeb69a1d536fb..6dbaade2d929c7a425eca520d34b28c2a3a30369 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ build src/gen-res/ src/gen/ -out/ +out*/ *.class diff --git a/build.gradle b/build.gradle index aff97ea032eb35353c0e1c547483b5fea7c29faa..86a6d5b06a320934adbda772b1d47aa29088e0dd 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ apply plugin: "idea" sourceCompatibility = 1.8 -mainClassName = 'org.jastadd.relast.compiler.Compiler' +mainClassName = 'org.jastadd.relast.compiler.JastAddExtensionCompiler' repositories { jcenter() @@ -45,7 +45,7 @@ test { jar { manifest { - attributes "Main-Class": 'org.jastadd.relast.compiler.Compiler' + attributes "Main-Class": 'org.jastadd.relast.compiler.JastAddExtensionCompiler' } from { diff --git a/src/main/jastadd/RelAst.relast b/src/main/jastadd/RelAst.relast index b92cb924a986af6d13084792f597650e65329499..3de13a9562aaccff0b4202e79500d251f0f043a1 100644 --- a/src/main/jastadd/RelAst.relast +++ b/src/main/jastadd/RelAst.relast @@ -5,7 +5,8 @@ Program ::= GrammarFile *; abstract Grammar ::= Declaration*; GrammarFile : Grammar ::= <FileName> ; -abstract Declaration; +abstract Declaration ::= Comment*; +EmptyDeclaration : Declaration; TypeDecl:Declaration ::= <Name> <Abstract:boolean> Component*; @@ -46,4 +47,8 @@ UnnamedRole : Role ; rel Role.Type <-> TypeDecl.Role*; // comments -Comment : Declaration ::= <Text>; +abstract Comment ::= <Text>; +WhitespaceComment : Comment; +SingleLineComment : Comment; +MultiLineComment : Comment; +DocComment : Comment; diff --git a/src/main/jastadd/backend/AbstractGrammar.jadd b/src/main/jastadd/backend/AbstractGrammar.jadd index 713efeed9b83ff4d5833323dd00e2e3fddeda50c..da803c5448c6129d4123bc55ad80cc1abedcc6e9 100644 --- a/src/main/jastadd/backend/AbstractGrammar.jadd +++ b/src/main/jastadd/backend/AbstractGrammar.jadd @@ -29,10 +29,14 @@ aspect BackendAbstractGrammar { } } - abstract public void Declaration.generateAbstractGrammar(StringBuilder b); + public void Declaration.generateAbstractGrammar(StringBuilder b) { + for (Comment comment : getCommentList()) { + comment.generateAbstractGrammar(b); + } + } - public void Comment.generateAbstractGrammar(StringBuilder b) { - b.append(getText()).append("\n"); + public void EmptyDeclaration.generateAbstractGrammar(StringBuilder b) { + super.generateAbstractGrammar(b); } public void TypeDecl.generateAbstractGrammar(StringBuilder b) { @@ -48,7 +52,8 @@ aspect BackendAbstractGrammar { b.append(" "); component.generateAbstractGrammar(b); } - b.append(";\n"); + b.append(";"); + super.generateAbstractGrammar(b); } public abstract void Component.generateAbstractGrammar(StringBuilder b); @@ -135,14 +140,17 @@ aspect BackendAbstractGrammar { b.append(">"); } - abstract public void Relation.generateAbstractGrammar(StringBuilder b); + public void Relation.generateAbstractGrammar(StringBuilder b) { + super.generateAbstractGrammar(b); + } public void DirectedRelation.generateAbstractGrammar(StringBuilder b) { b.append("rel "); getSource().generateAbstractGrammar(b); b.append(" -> "); getTarget().generateAbstractGrammar(b); - b.append(";\n"); + b.append(";"); + super.generateAbstractGrammar(b); } public void BidirectionalRelation.generateAbstractGrammar(StringBuilder b) { @@ -150,7 +158,8 @@ aspect BackendAbstractGrammar { getLeft().generateAbstractGrammar(b); b.append(" <-> "); getRight().generateAbstractGrammar(b); - b.append(";\n"); + b.append(";"); + super.generateAbstractGrammar(b); } abstract public void Role.generateAbstractGrammar(StringBuilder b); @@ -171,5 +180,22 @@ aspect BackendAbstractGrammar { b.append(getType().getName()); } + abstract public void Comment.generateAbstractGrammar(StringBuilder b); + + public void WhitespaceComment.generateAbstractGrammar(StringBuilder b) { + b.append(getText()); + } + + public void SingleLineComment.generateAbstractGrammar(StringBuilder b) { + b.append("//").append(getText()).append("\n"); + } + + public void MultiLineComment.generateAbstractGrammar(StringBuilder b) { + b.append("/*").append(getText()).append("*/"); + } + + public void DocComment.generateAbstractGrammar(StringBuilder b) { + b.append("/**").append(getText()).append("*/"); + } } diff --git a/src/main/jastadd/parser/RelAst.parser b/src/main/jastadd/parser/RelAst.parser index c05c959f6f5f7729549024daeb2da3ea1d315a88..b483f830759d3e501d2a96d9d3dc07c9eb0074b2 100644 --- a/src/main/jastadd/parser/RelAst.parser +++ b/src/main/jastadd/parser/RelAst.parser @@ -1,16 +1,31 @@ GrammarFile goal - = declaration.d goal.p {: p.getDeclarationList().insertChild(d, 0); return p; :} - | {: return new GrammarFile(); :} + = comment_list.c {: return new EmptyDeclaration(c); :} + | grammar_file +; + +GrammarFile grammar_file + = declaration.d grammar_file.f {: f.getDeclarationList().insertChild(d, 0); return f; :} + | {: return new GrammarFile(); :} ; Declaration declaration - = type_decl - | relation - | comment + = type_decl.d comment_list.c {: d.setCommentList(c); return d; :} + | relation.r comment_list.c {: r.setCommentList(c); return r; :} +; + +// this method would be create by the JAstAddParser from a usage of +// 'comment+' in a rule, but only for the standard list class 'List'. +JastAddList comment_list + = comment.n {: return new JastAddList().add(n); :} + | comment_list.l comment.n {: return l.add(n); :} ; Comment comment - = COMMENT {: return new Comment(COMMENT); :}; + = WHITESPACE.c {: return new WhitespaceComment(c); :} + | MULTILINECOMMENT.c {: return new MultiLineComment(c.substring(2,c.length()-2)); :} + | DOCCOMMENT.c {: return new DocComment(c.substring(3,c.length()-2)); :} + | SINGLELINECOMMENT.c {: return new SingleLineComment(c.substring(2)); :} +; TypeDecl type_decl = ID components_opt.c SCOL diff --git a/src/main/jastadd/scanner/Header.flex b/src/main/jastadd/scanner/Header.flex index 73120659873e1498b07be82eb29d82fe3d1b5112..c343dcd66a49ccac8233775dde7a9282e87e8b87 100644 --- a/src/main/jastadd/scanner/Header.flex +++ b/src/main/jastadd/scanner/Header.flex @@ -13,5 +13,8 @@ import org.jastadd.relast.parser.RelAstParser.Terminals; %yylexthrow beaver.Scanner.Exception %scanerror RelAstScanner.ScannerError +%x COMMENT +%s DECLARATION + %line %column diff --git a/src/main/jastadd/scanner/Keywords.flex b/src/main/jastadd/scanner/Keywords.flex index 76e6359ae9967bd16220eb09d5dac4a7ed5389cc..76397b9efcd1c748a3a1eb69675d23dbd4042178 100644 --- a/src/main/jastadd/scanner/Keywords.flex +++ b/src/main/jastadd/scanner/Keywords.flex @@ -1,2 +1,4 @@ -"abstract" { return sym(Terminals.ABSTRACT); } -"rel" { return sym(Terminals.RELATION); } +<YYINITIAL,DECLARATION> { + "abstract" { yybegin(DECLARATION); return sym(Terminals.ABSTRACT); } + "rel" { yybegin(DECLARATION); return sym(Terminals.RELATION); } +} diff --git a/src/main/jastadd/scanner/Macros.flex b/src/main/jastadd/scanner/Macros.flex index 167f51b882cfce44a1d225503589b76ad38012b9..f67e92684f270e5eb75d87369b2a281781774d53 100644 --- a/src/main/jastadd/scanner/Macros.flex +++ b/src/main/jastadd/scanner/Macros.flex @@ -1,5 +1,5 @@ -WhiteSpace = [ ] | \t | \f | \n | \r | \r\n -ID = [a-zA-Z$_][a-zA-Z0-9$_]* -TraditionalComment = [/][*][^*]*[*]+([^*/][^*]*[*]+)*[/] -EndOfLineComment = "//" [^\n\r]* -Comment = {TraditionalComment} | {EndOfLineComment} +WhiteSpace = [ ] | \t | \f | \n | \r | \r\n +ID = [a-zA-Z$_][a-zA-Z0-9$_]* +MultiLineComment = [/][*][^*]+[*]+([^*/][^*]*[*]+)*[/] +DocComment = [/][*][*][^*]*[*]+([^*/][^*]*[*]+)*[/] +SingleLineComment = [/][/] [^\n\r]* (\n | \r | \r\n) diff --git a/src/main/jastadd/scanner/RulesPostamble.flex b/src/main/jastadd/scanner/RulesPostamble.flex index 1ae39c6244e39c6f2bd2b0c6de41f088b3a51cde..5460a0b87e2002c06430aa5cc88abf0b39f30b9f 100644 --- a/src/main/jastadd/scanner/RulesPostamble.flex +++ b/src/main/jastadd/scanner/RulesPostamble.flex @@ -1,4 +1,7 @@ -{ID} { return sym(Terminals.ID); } -<<EOF>> { return sym(Terminals.EOF); } - -[^] { throw new ScannerError((yyline+1) +"," + (yycolumn+1) + ": Illegal character <"+yytext()+">"); } +<YYINITIAL,DECLARATION> { + {ID} { yybegin(DECLARATION); return sym(Terminals.ID); } + [^] { throw new ScannerError((yyline+1) +"," + (yycolumn+1) + ": Illegal character <"+yytext()+">"); } +} +<YYINITIAL,DECLARATION,COMMENT> { + <<EOF>> { return sym(Terminals.EOF); } +} diff --git a/src/main/jastadd/scanner/RulesPreamble.flex b/src/main/jastadd/scanner/RulesPreamble.flex index a55876d60989c0f6640ad62e9478cdca48c0573d..63081dec970d3bd18bad2f4b2a387f578b01aabc 100644 --- a/src/main/jastadd/scanner/RulesPreamble.flex +++ b/src/main/jastadd/scanner/RulesPreamble.flex @@ -1,5 +1,16 @@ %% -{WhiteSpace} { /* ignore */ } -{Comment} { return sym(Terminals.COMMENT); } +<DECLARATION> { + {WhiteSpace} { /* ignore */ } + {MultiLineComment} { /* ignore */ } + {DocComment} { /* ignore */ } + {SingleLineComment} { /* ignore */ } +} + +<YYINITIAL,COMMENT> { + {WhiteSpace}+ { yybegin(YYINITIAL); return sym(Terminals.WHITESPACE); } + {MultiLineComment} { yybegin(YYINITIAL); return sym(Terminals.MULTILINECOMMENT); } + {DocComment} { yybegin(YYINITIAL); return sym(Terminals.DOCCOMMENT); } + {SingleLineComment} { yybegin(YYINITIAL); return sym(Terminals.SINGLELINECOMMENT); } +} diff --git a/src/main/jastadd/scanner/Symbols.flex b/src/main/jastadd/scanner/Symbols.flex index 2990e998f9373f259b0346d2ae3c24bb6a781452..7f05c115b570fd3681617106bb77bc25bb79d7b2 100644 --- a/src/main/jastadd/scanner/Symbols.flex +++ b/src/main/jastadd/scanner/Symbols.flex @@ -1,15 +1,17 @@ -";" { return sym(Terminals.SCOL); } -":" { return sym(Terminals.COL); } -"::=" { return sym(Terminals.ASSIGN); } -"*" { return sym(Terminals.STAR); } -"." { return sym(Terminals.DOT); } -"," { return sym(Terminals.COMMA); } -"<" { return sym(Terminals.LT); } -">" { return sym(Terminals.GT); } -"[" { return sym(Terminals.LBRACKET); } -"]" { return sym(Terminals.RBRACKET); } -"/" { return sym(Terminals.SLASH); } -"?" { return sym(Terminals.QUESTION_MARK); } -"->" { return sym(Terminals.RIGHT); } -"<-" { return sym(Terminals.LEFT); } -"<->" { return sym(Terminals.BIDIRECTIONAL); } +<YYINITIAL,DECLARATION> { + ";" { yybegin(COMMENT); return sym(Terminals.SCOL); } + ":" { yybegin(DECLARATION); return sym(Terminals.COL); } + "::=" { yybegin(DECLARATION); return sym(Terminals.ASSIGN); } + "*" { yybegin(DECLARATION); return sym(Terminals.STAR); } + "." { yybegin(DECLARATION); return sym(Terminals.DOT); } + "," { yybegin(DECLARATION); return sym(Terminals.COMMA); } + "<" { yybegin(DECLARATION); return sym(Terminals.LT); } + ">" { yybegin(DECLARATION); return sym(Terminals.GT); } + "[" { yybegin(DECLARATION); return sym(Terminals.LBRACKET); } + "]" { yybegin(DECLARATION); return sym(Terminals.RBRACKET); } + "/" { yybegin(DECLARATION); return sym(Terminals.SLASH); } + "?" { yybegin(DECLARATION); return sym(Terminals.QUESTION_MARK); } + "->" { yybegin(DECLARATION); return sym(Terminals.RIGHT); } + "<-" { yybegin(DECLARATION); return sym(Terminals.LEFT); } + "<->" { yybegin(DECLARATION); return sym(Terminals.BIDIRECTIONAL); } +} diff --git a/src/main/java/org/jastadd/JastAddConfiguration.java b/src/main/java/org/jastadd/JastAddConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..30774005356c0eb30354b7fb76e1bafe67d00873 --- /dev/null +++ b/src/main/java/org/jastadd/JastAddConfiguration.java @@ -0,0 +1,177 @@ +/* Copyright (c) 2013-2015, Jesper Öqvist <jesper.oqvist@cs.lth.se> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Lund University nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.jastadd; + +import org.jastadd.option.ArgumentParser; +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 { + + /** + * Indicates if there were unknown command-line options + */ + final boolean unknownOptions; + + private boolean isJastAddCompliant; + + public boolean isJastAddCompliant() { + return isJastAddCompliant; + } + + /** + * 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(); + this.isJastAddCompliant = isJastAddCompliant; + if (isJastAddCompliant) { + Collection<Option<?>> jastAddOptions = allJastAddOptions(); + 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); + } + } + } else { + argParser.addOptions(extraOptions); + } + + unknownOptions = !argParser.parseArgs(args, err); + filenames = argParser.getFilenames(); + } + + /** + * @return all files + */ + @Override + public Collection<String> getFiles() { + return Collections.unmodifiableCollection(filenames); + } + + private Collection<Option<?>> allJastAddOptions() { + Collection<Option<?>> allOptions = new LinkedList<>(); + allOptions.add(ASTNodeOption); + allOptions.add(ListOption); + allOptions.add(OptOption); + allOptions.add(jjtreeOption); + allOptions.add(grammarOption); + allOptions.add(generateAnnotations); + allOptions.add(defaultMapOption); + allOptions.add(defaultSetOption); + allOptions.add(lazyMapsOption); + allOptions.add(privateOption); + allOptions.add(rewriteOption); + allOptions.add(beaverOption); + allOptions.add(lineColumnNumbersOption); + allOptions.add(visitCheckOption); + allOptions.add(traceVisitCheckOption); + allOptions.add(cacheCycleOption); + allOptions.add(componentCheckOption); + allOptions.add(inhEqCheckOption); + allOptions.add(suppressWarningsOption); + allOptions.add(refineLegacyOption); + allOptions.add(licenseOption); + allOptions.add(debugOption); + allOptions.add(outputDirOption); + allOptions.add(staticStateOption); + allOptions.add(tracingOption); + allOptions.add(flushOption); + allOptions.add(packageNameOption); + allOptions.add(versionOption); + allOptions.add(helpOption); + allOptions.add(printNonStandardOptionsOption); + allOptions.add(indentOption); + allOptions.add(minListSizeOption); + allOptions.add(cacheOption); + allOptions.add(incrementalOption); + + // New since 2.1.11. + allOptions.add(dotOption); + allOptions.add(ASTNodeSuperOption); + allOptions.add(generateImplicitsOption); + + // New since 2.1.12. + allOptions.add(stateClassNameOption); + + // New since 2.2.1: + allOptions.add(safeLazyOption); + + // New since 2.2.3: + allOptions.add(statisticsOption); + + // New since 2.2.4: + allOptions.add(emptyContainerSingletons); + allOptions.add(concurrentOption); + allOptions.add(numThreadsOption); + allOptions.add(concurrentMap); + + // 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; + } + +} diff --git a/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java b/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java new file mode 100644 index 0000000000000000000000000000000000000000..1330ebe284b62a90480959eb35eb23dbda831540 --- /dev/null +++ b/src/main/java/org/jastadd/relast/compiler/AbstractCompiler.java @@ -0,0 +1,63 @@ +package org.jastadd.relast.compiler; + +import org.jastadd.JastAddConfiguration; +import org.jastadd.option.ArgumentParser; +import org.jastadd.option.Option; + +import java.util.ArrayList; + +public abstract class AbstractCompiler { + + private final boolean jastAddCompliant; + protected ArrayList<Option<?>> options; + private String name; + private ArgumentParser commandLine; + + private JastAddConfiguration 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!"); + } + return configuration; + } + + public int run(String[] args) throws CompilerException { + + options = new ArrayList<>(); + initOptions(); + configuration = new JastAddConfiguration(args, System.err, jastAddCompliant, options); + + return compile(); + } + + abstract int compile() throws CompilerException; + + protected void initOptions() { + // there are no options by default + } + + protected <OptionType extends Option<?>> OptionType addOption(OptionType option) { + options.add(option); + return option; + } + + protected int error(String message) { + System.err.println("Error: " + message); + System.err.println(); + System.err.println("Usage: java -jar " + name + ".jar [--option1] [--option2=value] ... <filename1> <filename2> ... "); + System.err.println("Options:"); + commandLine.printHelp(System.err); + return 1; + } + + public String getName() { + return name; + } +} + diff --git a/src/main/java/org/jastadd/relast/compiler/Compiler.java b/src/main/java/org/jastadd/relast/compiler/Compiler.java deleted file mode 100644 index 4f82148370b1ca1c49799d9a93cbce5151945d51..0000000000000000000000000000000000000000 --- a/src/main/java/org/jastadd/relast/compiler/Compiler.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.jastadd.relast.compiler; - -import beaver.Parser; -import org.jastadd.option.ArgumentParser; -import org.jastadd.option.Option; -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.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; - -public class Compiler { - - private ValueOption optionOutputDir; - private ValueOption optionInputDir; - - private ArrayList<Option<?>> options; - private ArgumentParser commandLine; - - public Compiler() { - options = new ArrayList<>(); - addOptions(); - } - - public static void main(String[] args) { - try { - new Compiler().run(args); - } catch (CompilerException e) { - System.err.println(e.getMessage()); - System.exit(-1); - } - } - - public int run(String[] args) throws CompilerException { - options = new ArrayList<>(); - addOptions(); - commandLine = new ArgumentParser(); - commandLine.addOptions(options); - boolean unknownOptions = !commandLine.parseArgs(args, System.err); - - Path inputPath; - if (optionInputDir.isMatched()) { - inputPath = Paths.get(optionInputDir.value()); - } else { - inputPath = Paths.get("."); - printMessage("No input dir is set. Assuming current directory '" + inputPath.toAbsolutePath().toString() + "'."); - } - - if (!inputPath.toFile().exists()) { - printMessage("Input path '" + inputPath.toAbsolutePath().toString() + "' does not exist. Exiting..."); - System.exit(-1); - } else if (!inputPath.toFile().isDirectory()) { - printMessage("Input path '" + inputPath.toAbsolutePath().toString() + "' is not a directory. Exiting..."); - System.exit(-1); - } - - Path outputPath; // should not be used, but otherwise there is a compiler warning - if (optionOutputDir.isMatched()) { - outputPath = Paths.get(optionOutputDir.value()); - } else { - outputPath = Paths.get("./gen/"); - printMessage("No output dir is set. Assuming '" + outputPath.toAbsolutePath().toString() + "'."); - } - - if (outputPath.toFile().exists() && !outputPath.toFile().isDirectory()) { - printMessage("Output path '" + inputPath.toAbsolutePath().toString() + "' exists, but is not a directory. Exiting..."); - } - - printMessage("Running RelAST Preprocessor"); - - if (unknownOptions) { - printMessage("Some options were unsupported!"); - } - - Program program = parseProgram(inputPath); - - printMessage("Writing output files"); - - for (GrammarFile grammarFile : program.getGrammarFileList()) { - // TODO decide and document what the file name should be, the full path or a simple name? - writeToFile(outputPath + grammarFile.getFileName() + "/Grammar.relast", grammarFile.generateAbstractGrammar()); - } - - writeToFile(outputPath + "/Grammar.relast", program.generateAbstractGrammar()); - return 0; - } - - private void printMessage(String message) { - System.out.println(message); - } - - private void writeToFile(String filename, String str) throws CompilerException { - try { - PrintWriter writer = new PrintWriter(filename); - writer.print(str); - writer.close(); - } catch (Exception e) { - throw new CompilerException("Could not write to file " + filename, e); - } - } - - private void addOptions() { - optionOutputDir = addOption(new ValueOption("outputDir", "target directory for the generated files.")); - optionInputDir = addOption(new ValueOption("inputDir", "input directory.")); - } - - private <OptionType extends Option<?>> OptionType addOption(OptionType option) { - options.add(option); - return option; - } - - private Program parseProgram(Path inputPath) throws CompilerException { - Program program = new Program(); - - try (DirectoryStream<Path> stream = Files.newDirectoryStream(inputPath, "*.relast")) { - RelAstParser parser = new RelAstParser(); - stream.forEach(path -> { - try (BufferedReader reader = Files.newBufferedReader(path)) { - RelAstScanner scanner = new RelAstScanner(reader); - GrammarFile inputGrammar = (GrammarFile) parser.parse(scanner); - inputGrammar.dumpTree(System.out); - program.addGrammarFile(inputGrammar); - inputGrammar.treeResolveAll(); - } catch (IOException | Parser.Exception e) { - printMessage("Could not parse grammar file " + path); - e.printStackTrace(); - } - }); - } catch (IOException e) { - printMessage("Unable to iterate over input path '" + inputPath.toAbsolutePath().toString() + "'. Exiting..."); - e.printStackTrace(); - System.exit(-1); - } - - - return program; - } - - protected int error(String message) { - System.err.println("Error: " + message); - System.err.println(); - System.err.println("Usage: java -jar relast.jar [--option1] [--option2=value] ... <filename1> <filename2> ... "); - System.err.println("Options:"); - commandLine.printHelp(System.err); - return 1; - } - - public static class CompilerException extends Exception { - CompilerException(String message, Throwable cause) { - super(message, cause); - } - } -} - diff --git a/src/main/java/org/jastadd/relast/compiler/CompilerException.java b/src/main/java/org/jastadd/relast/compiler/CompilerException.java new file mode 100644 index 0000000000000000000000000000000000000000..6949c308da5f431c8bf4a6a1e32d3722f1a9d744 --- /dev/null +++ b/src/main/java/org/jastadd/relast/compiler/CompilerException.java @@ -0,0 +1,11 @@ +package org.jastadd.relast.compiler; + +public class CompilerException extends Exception { + public CompilerException(String message, Throwable cause) { + super(message, cause); + } + + public CompilerException(String message) { + super(message); + } +} diff --git a/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java new file mode 100644 index 0000000000000000000000000000000000000000..7d6949f5ff7ec74d2e1963e564bff9d633d4fbec --- /dev/null +++ b/src/main/java/org/jastadd/relast/compiler/RelastSourceToSourceCompiler.java @@ -0,0 +1,144 @@ +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 { + + + 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); + } catch (CompilerException e) { + System.err.println(e.getMessage()); + System.exit(-1); + } + } + + 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")); + } + + @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); + + printMessage("Writing output files"); + + for (GrammarFile grammarFile : program.getGrammarFileList()) { + // 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/test/java/org/jastadd/ros2rag/tests/RelAstTest.java b/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java index becda3fbdfb3152aa380b942a9cd1e76e32e4cc9..c87e51f7dbc70644b621998548d4f942160e0ea9 100644 --- a/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java +++ b/src/test/java/org/jastadd/ros2rag/tests/RelAstTest.java @@ -1,6 +1,7 @@ package org.jastadd.ros2rag.tests; -import org.jastadd.relast.compiler.Compiler; +import org.jastadd.relast.compiler.CompilerException; +import org.jastadd.relast.compiler.RelastSourceToSourceCompiler; import org.junit.jupiter.api.Test; import java.io.File; @@ -11,7 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class RelAstTest { - void transform(String inputDir, String outputDir) throws Compiler.CompilerException { + 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"); @@ -28,15 +29,17 @@ public class RelAstTest { } String[] args = { - "--outputDir=" + outputDir, - "--inputDir=" + inputDir + "--outputBaseDir=" + outputDir, + "--inputBaseDir=" + inputDir, + "Example.relast" }; - new Compiler().run(args); + new RelastSourceToSourceCompiler("testCompiler", jastAddCompliant).run(args); } @Test - void transformMinimalExample() throws Compiler.CompilerException { - transform("src/test/resources/in", "src/test/resources/out"); + 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"); } }