502 lines
17 KiB
Java
502 lines
17 KiB
Java
/**
|
|
[The "BSD licence"]
|
|
|
|
ANTLR - Copyright (c) 2005-2008 Terence Parr
|
|
Maven Plugin - Copyright (c) 2009 Jim Idle
|
|
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. 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.
|
|
3. The name of the author may not be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
|
|
*/
|
|
|
|
/* ========================================================================
|
|
* This is the definitive ANTLR3 Mojo set. All other sets are belong to us.
|
|
*/
|
|
package org.antlr.mojo.antlr3;
|
|
|
|
import antlr.RecognitionException;
|
|
import antlr.TokenStreamException;
|
|
import org.apache.maven.plugin.AbstractMojo;
|
|
import org.apache.maven.plugin.MojoExecutionException;
|
|
import org.apache.maven.plugin.MojoFailureException;
|
|
import org.apache.maven.project.MavenProject;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.Collections;
|
|
import java.util.HashSet;
|
|
import java.util.Set;
|
|
import org.antlr.Tool;
|
|
import org.apache.maven.plugin.logging.Log;
|
|
import org.codehaus.plexus.compiler.util.scan.InclusionScanException;
|
|
import org.codehaus.plexus.compiler.util.scan.SimpleSourceInclusionScanner;
|
|
import org.codehaus.plexus.compiler.util.scan.SourceInclusionScanner;
|
|
import org.codehaus.plexus.compiler.util.scan.mapping.SourceMapping;
|
|
import org.codehaus.plexus.compiler.util.scan.mapping.SuffixMapping;
|
|
|
|
/**
|
|
* Parses ANTLR grammar files {@code *.g} and transforms them into Java source
|
|
* files.
|
|
*
|
|
* @goal antlr
|
|
* @phase generate-sources
|
|
* @requiresDependencyResolution compile
|
|
* @requiresProject true
|
|
*
|
|
* @author <a href="mailto:jimi@temporal-wave.com">Jim Idle</a>
|
|
*/
|
|
public class Antlr3Mojo
|
|
extends AbstractMojo {
|
|
|
|
// First, let's deal with the options that the ANTLR tool itself
|
|
// can be configured by.
|
|
//
|
|
/**
|
|
* If set to true, then after the tool has processed an input grammar file
|
|
* it will report various statistics about the parser, such as information
|
|
* on cyclic DFAs, which rules may use backtracking, and so on.
|
|
*
|
|
* @parameter default-value="false"
|
|
*/
|
|
protected boolean report;
|
|
/**
|
|
* If set to true, then the ANTLR tool will print a version of the input
|
|
* grammar(s) which are stripped of any embedded actions.
|
|
*
|
|
* @parameter default-value="false"
|
|
*/
|
|
protected boolean printGrammar;
|
|
/**
|
|
* If set to true, then the code generated by the ANTLR code generator will
|
|
* be set to debug mode. This means that when run, the code will 'hang' and
|
|
* wait for a debug connection on a TCP port (49100 by default).
|
|
*
|
|
* @parameter default-value="false"
|
|
*/
|
|
protected boolean debug;
|
|
/**
|
|
* If set to true, then the generated parser will compute and report profile
|
|
* information at runtime.
|
|
*
|
|
* @parameter default-value="false"
|
|
*/
|
|
protected boolean profile;
|
|
/**
|
|
* If set to true, then the ANTLR tool will generate a description of the
|
|
* NFA for each rule in <a href="http://www.graphviz.org">Dot format</a>
|
|
*
|
|
* @parameter default-value="false"
|
|
*/
|
|
protected boolean nfa;
|
|
/**
|
|
* If set to true, then the ANTLR tool will generate a description of the
|
|
* DFA for each decision in the grammar in
|
|
* <a href="http://www.graphviz.org">Dot format</a>.
|
|
*
|
|
* @parameter default-value="false"
|
|
*/
|
|
protected boolean dfa;
|
|
/**
|
|
* If set to true, the generated parser code will log rule entry and exit
|
|
* points to stdout ({@link System#out} for the Java target) as an aid to
|
|
* debugging.
|
|
*
|
|
* @parameter default-value="false"
|
|
*/
|
|
protected boolean trace;
|
|
/**
|
|
* If this parameter is set, it indicates that any warning or error messages
|
|
* returned by ANLTR, should be formatted in the specified way. Currently,
|
|
* ANTLR supports the built-in formats {@code antlr}, {@code gnu} and
|
|
* {@code vs2005}.
|
|
*
|
|
* @parameter default-value="antlr"
|
|
*/
|
|
protected String messageFormat;
|
|
/**
|
|
* If set to true, then ANTLR will report verbose messages during the code
|
|
* generation process. This includes the names of files, the version of
|
|
* ANTLR, and more.
|
|
*
|
|
* @parameter default-value="true"
|
|
*/
|
|
protected boolean verbose;
|
|
|
|
/**
|
|
* The maximum number of alternatives allowed in an inline switch statement.
|
|
* Beyond this, ANTLR will not generate a switch statement for the DFA.
|
|
*
|
|
* @parameter default-value="300"
|
|
*/
|
|
private int maxSwitchCaseLabels;
|
|
|
|
/**
|
|
* The minimum number of alternatives for ANTLR to generate a switch
|
|
* statement. For decisions with fewer alternatives, an if/else if/else
|
|
* statement will be used instead.
|
|
*
|
|
* @parameter default-value="3"
|
|
*/
|
|
private int minSwitchAlts;
|
|
|
|
/* --------------------------------------------------------------------
|
|
* The following are Maven specific parameters, rather than specific
|
|
* options that the ANTLR tool can use.
|
|
*/
|
|
|
|
/**
|
|
* Provides an explicit list of all the grammars that should be included in
|
|
* the generate phase of the plugin. Note that the plugin is smart enough to
|
|
* realize that imported grammars should be included but not acted upon
|
|
* directly by the ANTLR Tool.
|
|
* <p/>
|
|
* A set of Ant-like inclusion patterns used to select files from the source
|
|
* directory for processing. By default, the pattern <code>**/*.g</code>
|
|
* is used to select grammar files.
|
|
*
|
|
* @parameter
|
|
*/
|
|
protected Set<String> includes = new HashSet<String>();
|
|
/**
|
|
* A set of Ant-like exclusion patterns used to prevent certain files from
|
|
* being processed. By default, this set is empty such that no files are
|
|
* excluded.
|
|
*
|
|
* @parameter
|
|
*/
|
|
protected Set<String> excludes = new HashSet<String>();
|
|
/**
|
|
* The current Maven project.
|
|
*
|
|
* @parameter expression="${project}"
|
|
* @required
|
|
* @readonly
|
|
*/
|
|
protected MavenProject project;
|
|
/**
|
|
* The directory where the ANTLR grammar files ({@code *.g}) are located.
|
|
*
|
|
* @parameter default-value="${basedir}/src/main/antlr3"
|
|
*/
|
|
private File sourceDirectory;
|
|
/**
|
|
* The directory where the parser files generated by ANTLR will be stored.
|
|
* The directory will be registered as a compile source root of the project
|
|
* such that the generated files will participate in later build phases like
|
|
* compiling and packaging.
|
|
*
|
|
* @parameter default-value="${project.build.directory}/generated-sources/antlr3"
|
|
* @required
|
|
*/
|
|
private File outputDirectory;
|
|
/**
|
|
* Location for imported token files, e.g. {@code *.tokens} and imported
|
|
* grammars. Note that ANTLR will not try to process grammars that it finds
|
|
* to be imported into other grammars (in the same processing session).
|
|
*
|
|
* @parameter default-value="${basedir}/src/main/antlr3/imports"
|
|
*/
|
|
private File libDirectory;
|
|
|
|
public File getSourceDirectory() {
|
|
return sourceDirectory;
|
|
}
|
|
|
|
public File getOutputDirectory() {
|
|
return outputDirectory;
|
|
}
|
|
|
|
public File getLibDirectory() {
|
|
return libDirectory;
|
|
}
|
|
|
|
void addSourceRoot(File outputDir) {
|
|
project.addCompileSourceRoot(outputDir.getPath());
|
|
}
|
|
/**
|
|
* An instance of the ANTLR tool build.
|
|
*/
|
|
protected Tool tool;
|
|
|
|
/**
|
|
* The main entry point for this Mojo, it is responsible for converting
|
|
* ANTLR 3.x grammars into the target language specified by the grammar.
|
|
*
|
|
* @throws MojoExecutionException if a configuration or grammar error causes
|
|
* the code generation process to fail
|
|
* @throws MojoFailureException if an instance of the ANTLR 3 {@link Tool}
|
|
* cannot be created
|
|
*/
|
|
public void execute()
|
|
throws MojoExecutionException, MojoFailureException {
|
|
|
|
Log log = getLog();
|
|
|
|
// Check to see if the user asked for debug information, then dump all the
|
|
// parameters we have picked up if they did.
|
|
//
|
|
if (log.isDebugEnabled()) {
|
|
|
|
// Excludes
|
|
//
|
|
for (String e : excludes) {
|
|
log.debug("ANTLR: Exclude: " + e);
|
|
}
|
|
|
|
// Includes
|
|
//
|
|
for (String e : includes) {
|
|
log.debug("ANTLR: Include: " + e);
|
|
}
|
|
|
|
// Output location
|
|
//
|
|
log.debug("ANTLR: Output: " + outputDirectory);
|
|
|
|
// Library directory
|
|
//
|
|
log.debug("ANTLR: Library: " + libDirectory);
|
|
|
|
// Flags
|
|
//
|
|
log.debug("ANTLR: report : " + report);
|
|
log.debug("ANTLR: printGrammar : " + printGrammar);
|
|
log.debug("ANTLR: debug : " + debug);
|
|
log.debug("ANTLR: profile : " + profile);
|
|
log.debug("ANTLR: nfa : " + nfa);
|
|
log.debug("ANTLR: dfa : " + dfa);
|
|
log.debug("ANTLR: trace : " + trace);
|
|
log.debug("ANTLR: messageFormat : " + messageFormat);
|
|
log.debug("ANTLR: maxSwitchCaseLabels : " + maxSwitchCaseLabels);
|
|
log.debug("ANTLR: minSwitchAlts : " + minSwitchAlts);
|
|
log.debug("ANTLR: verbose : " + verbose);
|
|
}
|
|
|
|
// Ensure that the output directory path is all in tact so that
|
|
// ANTLR can just write into it.
|
|
//
|
|
File outputDir = getOutputDirectory();
|
|
|
|
if (!outputDir.exists()) {
|
|
outputDir.mkdirs();
|
|
}
|
|
|
|
// First thing we need is an instance of the ANTLR 3.1 build tool
|
|
//
|
|
try {
|
|
// ANTLR Tool buld interface
|
|
//
|
|
tool = new Tool();
|
|
} catch (Exception e) {
|
|
log.error("The attempt to create the ANTLR build tool failed, see exception report for details");
|
|
|
|
throw new MojoFailureException("Jim failed you!");
|
|
}
|
|
|
|
// Next we need to set the options given to us in the pom into the
|
|
// tool instance we have created.
|
|
//
|
|
tool.setDebug(debug);
|
|
tool.setGenerate_DFA_dot(dfa);
|
|
tool.setGenerate_NFA_dot(nfa);
|
|
tool.setProfile(profile);
|
|
tool.setReport(report);
|
|
tool.setPrintGrammar(printGrammar);
|
|
tool.setTrace(trace);
|
|
tool.setVerbose(verbose);
|
|
tool.setMessageFormat(messageFormat);
|
|
tool.setMaxSwitchCaseLabels(maxSwitchCaseLabels);
|
|
tool.setMinSwitchAlts(minSwitchAlts);
|
|
|
|
// Where do we want ANTLR to produce its output? (Base directory)
|
|
//
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug("Output directory base will be " + outputDirectory.getAbsolutePath());
|
|
}
|
|
tool.setOutputDirectory(outputDirectory.getAbsolutePath());
|
|
|
|
// Tell ANTLR that we always want the output files to be produced in the output directory
|
|
// using the same relative path as the input file was to the input directory.
|
|
//
|
|
tool.setForceRelativeOutput(true);
|
|
|
|
// Where do we want ANTLR to look for .tokens and import grammars?
|
|
//
|
|
tool.setLibDirectory(libDirectory.getAbsolutePath());
|
|
|
|
if (!sourceDirectory.exists()) {
|
|
if (log.isInfoEnabled()) {
|
|
log.info("No ANTLR grammars to compile in " + sourceDirectory.getAbsolutePath());
|
|
}
|
|
return;
|
|
} else {
|
|
if (log.isInfoEnabled()) {
|
|
log.info("ANTLR: Processing source directory " + sourceDirectory.getAbsolutePath());
|
|
}
|
|
}
|
|
|
|
// Set working directory for ANTLR to be the base source directory
|
|
//
|
|
tool.setInputDirectory(sourceDirectory.getAbsolutePath());
|
|
|
|
try {
|
|
|
|
// Now pick up all the files and process them with the Tool
|
|
//
|
|
processGrammarFiles(sourceDirectory, outputDirectory);
|
|
|
|
} catch (InclusionScanException ie) {
|
|
|
|
log.error(ie);
|
|
throw new MojoExecutionException("Fatal error occured while evaluating the names of the grammar files to analyze");
|
|
|
|
} catch (Exception e) {
|
|
|
|
getLog().error(e);
|
|
throw new MojoExecutionException(e.getMessage());
|
|
}
|
|
|
|
|
|
|
|
tool.process();
|
|
|
|
// If any of the grammar files caused errors but did nto throw exceptions
|
|
// then we should have accumulated errors in the counts
|
|
//
|
|
if (tool.getNumErrors() > 0) {
|
|
throw new MojoExecutionException("ANTLR caught " + tool.getNumErrors() + " build errors.");
|
|
}
|
|
|
|
// All looks good, so we need to tel Maven about the sources that
|
|
// we just created.
|
|
//
|
|
if (project != null) {
|
|
// Tell Maven that there are some new source files underneath
|
|
// the output directory.
|
|
//
|
|
addSourceRoot(this.getOutputDirectory());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
* @param sourceDirectory
|
|
* @param outputDirectory
|
|
* @throws TokenStreamException
|
|
* @throws RecognitionException
|
|
* @throws IOException
|
|
* @throws InclusionScanException
|
|
*/
|
|
private void processGrammarFiles(File sourceDirectory, File outputDirectory)
|
|
throws TokenStreamException, RecognitionException, IOException, InclusionScanException {
|
|
// Which files under the source set should we be looking for as grammar files
|
|
//
|
|
SourceMapping mapping = new SuffixMapping("g", Collections.<String>emptySet());
|
|
|
|
// What are the sets of includes (defaulted or otherwise).
|
|
//
|
|
Set<String> includes = getIncludesPatterns();
|
|
|
|
// Now, to the excludes, we need to add the imports directory
|
|
// as this is autoscanned for importd grammars and so is auto-excluded from the
|
|
// set of gramamr fiels we shuold be analyzing.
|
|
//
|
|
excludes.add("imports/**");
|
|
|
|
SourceInclusionScanner scan = new SimpleSourceInclusionScanner(includes, excludes);
|
|
|
|
scan.addSourceMapping(mapping);
|
|
Set<File> grammarFiles = scan.getIncludedSources(sourceDirectory, null);
|
|
|
|
if (grammarFiles.isEmpty()) {
|
|
if (getLog().isInfoEnabled()) {
|
|
getLog().info("No grammars to process");
|
|
}
|
|
} else {
|
|
|
|
// Tell the ANTLR tool that we want sorted build mode
|
|
//
|
|
tool.setMake(true);
|
|
|
|
// Iterate each grammar file we were given and add it into the tool's list of
|
|
// grammars to process.
|
|
//
|
|
for (File grammar : grammarFiles) {
|
|
|
|
if (getLog().isDebugEnabled()) {
|
|
getLog().debug("Grammar file '" + grammar.getPath() + "' detected.");
|
|
}
|
|
|
|
|
|
String relPath = findSourceSubdir(sourceDirectory, grammar.getPath()) + grammar.getName();
|
|
|
|
if (getLog().isDebugEnabled()) {
|
|
getLog().debug(" ... relative path is: " + relPath);
|
|
}
|
|
tool.addGrammarFile(relPath);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
public Set<String> getIncludesPatterns() {
|
|
if (includes == null || includes.isEmpty()) {
|
|
return Collections.singleton("**/*.g");
|
|
}
|
|
return includes;
|
|
}
|
|
|
|
/**
|
|
* Given the source directory File object and the full PATH to a
|
|
* grammar, produce the path to the named grammar file in relative
|
|
* terms to the {@code sourceDirectory}. This will then allow ANTLR to
|
|
* produce output relative to the base of the output directory and
|
|
* reflect the input organization of the grammar files.
|
|
*
|
|
* @param sourceDirectory The source directory {@link File} object
|
|
* @param grammarFileName The full path to the input grammar file
|
|
* @return The path to the grammar file relative to the source directory
|
|
*/
|
|
private String findSourceSubdir(File sourceDirectory, String grammarFileName) {
|
|
String srcPath = sourceDirectory.getPath() + File.separator;
|
|
|
|
if (!grammarFileName.startsWith(srcPath)) {
|
|
throw new IllegalArgumentException("expected " + grammarFileName + " to be prefixed with " + sourceDirectory);
|
|
}
|
|
|
|
File unprefixedGrammarFileName = new File(grammarFileName.substring(srcPath.length()));
|
|
if ( unprefixedGrammarFileName.getParent()!=null ) {
|
|
return unprefixedGrammarFileName.getParent() + File.separator;
|
|
}
|
|
else {
|
|
return "";
|
|
}
|
|
}
|
|
}
|