package org.codehaus.mojo.antlr3; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.antlr.Tool; import org.antlr.analysis.DFA; import org.antlr.codegen.CodeGenerator; import org.antlr.tool.BuildDependencyGenerator; import org.antlr.tool.Grammar; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.project.MavenProject; 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; import antlr.RecognitionException; import antlr.TokenStreamException; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** * Generate source code from ANTLRv3 grammar specifications. * * @author <a href="mailto:dave@badgers-in-foil.co.uk">David Holroyd</a> * @version $Id $ */ public abstract class Antlr3PluginMojo extends AbstractMojo { /** * A set of patterns matching files from the sourceDirectory that * should be processed as grammars. * * @parameter */ protected Set includes = new HashSet(); /** * A set of exclude patterns. * * @parameter */ protected Set excludes = new HashSet(); /** * Enables ANTLR-specific network debugging. Requires a tool able to * talk this protocol e.g. ANTLRWorks. * * @parameter default-value="false" */ protected boolean debug; /** * Generate a parser that logs rule entry/exit messages. * * @parameter default-value="false" */ protected boolean trace; /** * Generate a parser that computes profiling information. * * @parameter default-value="false" */ protected boolean profile; private Tool tool; /** * The number of milliseconds ANTLR will wait for analysis of each * alternative in the grammar to complete before giving up. * * @parameter default-value="0" */ private int conversionTimeout; /** * @parameter expression="${project}" * @required * @readonly */ protected MavenProject project; abstract File getSourceDirectory(); abstract File getOutputDirectory(); abstract File getLibDirectory(); abstract void addSourceRoot( File outputDir ); /** * @see org.apache.maven.plugin.Mojo#execute() */ public void execute() throws MojoExecutionException { File outputDir = getOutputDirectory(); if ( !outputDir.exists() ) { outputDir.mkdirs(); } tool = new Tool(); DFA.MAX_TIME_PER_DFA_CREATION = conversionTimeout; File libFile = getLibDirectory(); if ( libFile != null ) { if ( !libFile.exists() ) { libFile.mkdirs(); // create dir if it did not allready exist } tool.processArgs( new String[] { "-lib", libFile.getAbsolutePath() } ); } File srcDir = getSourceDirectory(); try { processGrammarFiles( srcDir, outputDir ); } catch ( Exception e ) { throw new MojoExecutionException( "", e ); } if ( project != null ) { addSourceRoot( outputDir ); } } private void processGrammarFiles( File sourceDirectory, File outputDirectory ) throws TokenStreamException, RecognitionException, IOException, InclusionScanException { SourceMapping mapping = new SuffixMapping( "g", Collections.EMPTY_SET ); Set includes = getIncludesPatterns(); SourceInclusionScanner scan = new SimpleSourceInclusionScanner( includes, excludes ); scan.addSourceMapping( mapping ); Set grammarFiles = scan.getIncludedSources( sourceDirectory, null ); if ( grammarFiles.isEmpty() ) { if ( getLog().isInfoEnabled() ) { getLog().info( "No grammars to process" ); } } else { List grammars = loadGrammarDependencies( grammarFiles, sourceDirectory, outputDirectory ); sortIntoBuildOrder( grammars ); boolean built = false; for ( Iterator i = grammars.iterator(); i.hasNext(); ) { built |= processGrammarFile( ( (GrammarInfo) i.next() ) ); } if ( !built && getLog().isInfoEnabled() ) { getLog().info( "No grammars processed; generated files are up to date" ); } } } private void sortIntoBuildOrder( List grammars ) { Collections.sort( grammars, new Comparator() { public int compare( Object o1, Object o2 ) { GrammarInfo a = (GrammarInfo) o1; GrammarInfo b = (GrammarInfo) o2; if ( a.dependsOn( b ) ) { return 1; } else if ( b.dependsOn( a ) ) { return -1; } return 0; } } ); } private List loadGrammarDependencies( Set grammarFiles, File sourceDirectory, File outputDirectory ) throws TokenStreamException, RecognitionException, IOException { List result = new ArrayList(); for ( Iterator i = grammarFiles.iterator(); i.hasNext(); ) { String grammarFileName = ( (File) i.next() ).getPath(); // Hack the output dir such that the output hierarchy will match the // source hierarchy. This way, grammar authors can arrange their // grammars in a structure that matches the package (assuming Java // output), and the generated files will not produce warnings/errors // from javac due to the path-prefix not matching the package-prefix. // (ANTLR sort-of does this itself, but only when grammar file names // are specified relative to $PWD) String sourceSubdir = findSourceSubdir( sourceDirectory, grammarFileName ); File outputSubdir = new File( outputDirectory, sourceSubdir ); tool.setOutputDirectory( outputSubdir.getPath() ); BuildDependencyGenerator dep = new BuildDependencyGenerator( tool, grammarFileName ); result.add( new GrammarInfo( dep, grammarFileName ) ); } return result; } public Set getIncludesPatterns() { if ( includes == null || includes.isEmpty() ) { return Collections.singleton( "**/*.g" ); } return includes; } private boolean processGrammarFile( GrammarInfo grammarInfo ) throws TokenStreamException, RecognitionException, IOException { List outputFiles = grammarInfo.getBuildDependency().getGeneratedFileList(); if ( AntlrHelper.buildRequired( grammarInfo.getGrammarFileName(), outputFiles ) ) { generate( grammarInfo.getGrammarFileName() ); return true; } return false; } private String findSourceSubdir( File sourceDirectory, String grammarFileName ) { String srcPath = sourceDirectory.getPath(); if ( !grammarFileName.startsWith( srcPath ) ) { throw new IllegalArgumentException( "expected " + grammarFileName + " to be prefixed with " + sourceDirectory ); } File unprefixedGrammarFileName = new File( grammarFileName.substring( srcPath.length() ) ); return unprefixedGrammarFileName.getParent(); } private void generate( String grammarFileName ) throws TokenStreamException, RecognitionException, IOException { if ( getLog().isInfoEnabled() ) { getLog().info( "Processing grammar " + grammarFileName ); } Grammar grammar = tool.getGrammar( grammarFileName ); processGrammar( grammar ); // now handle the lexer if one was created for a merged spec String lexerGrammarStr = grammar.getLexerGrammar(); if ( grammar.type == Grammar.COMBINED && lexerGrammarStr != null ) { String lexerGrammarFileName = grammar.getImplicitlyGeneratedLexerFileName(); Writer w = tool.getOutputFile( grammar, lexerGrammarFileName ); w.write( lexerGrammarStr ); w.close(); StringReader sr = new StringReader( lexerGrammarStr ); Grammar lexerGrammar = new Grammar(); lexerGrammar.setTool( tool ); File lexerGrammarFullFile = new File( tool.getFileDirectory( lexerGrammarFileName ), lexerGrammarFileName ); lexerGrammar.setFileName( lexerGrammarFullFile.toString() ); lexerGrammar.importTokenVocabulary( grammar ); lexerGrammar.setGrammarContent( sr ); sr.close(); processGrammar( lexerGrammar ); } } private void processGrammar( Grammar grammar ) { String language = (String) grammar.getOption( "language" ); if ( language != null ) { CodeGenerator generator = new CodeGenerator( tool, grammar, language ); grammar.setCodeGenerator( generator ); generator.setDebug( debug ); generator.setProfile( profile ); generator.setTrace( trace ); generator.genRecognizer(); } } }