/* * Copyright 2000-2004 The Apache Software Foundation * * Licensed 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. * * 2006-12-29: Modified to work for antlr3 by Jürgen Pfundt * 2007-01-04: Some minor correction after checking code with findBugs tool * 2007-02-10: Adapted the grammar type recognition to the changed naming * conventions for Tree Parser * 2007-10-17: Options "trace", "traceLexer", "traceParser" and "glib" emit * warnings when being used. * Added recognition of "parser grammar T". * Added options "nocollapse", "noprune". * ANTLR option "depend" is being used to resolve build dependencies. * 2007-11-15: Embedded Classpath statement had not been observed * with option depend="true" (Reported by Mats Behre) * 2008-03-31: Support the option conversiontimeout. (Jim Idle) * 2007-12-31: With option "depend=true" proceed even if first pass failed so * that ANTLR can spit out its errors * 2008-08-09: Inspecting environment variable ANTLR_HOME to detect and add * antlr- and stringtemplate libraries to the classpath * 2008-08-09: Removed routine checkGenerateFile. It got feeble with the * introduction of composed grammars, e.g. "import T.g" and after * a short struggle it started it's journey to /dev/null. * From now one it is always antlr itself via the depend option * which decides about dependecies * 2008-08-19: Dependency check for composed grammars added. * Might need some further improvements. */ package org.apache.tools.ant.antlr; import java.util.regex.*; import java.io.*; import java.util.Map; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.Execute; import org.apache.tools.ant.taskdefs.LogOutputStream; import org.apache.tools.ant.taskdefs.PumpStreamHandler; import org.apache.tools.ant.taskdefs.Redirector; import org.apache.tools.ant.types.Commandline; import org.apache.tools.ant.types.CommandlineJava; import org.apache.tools.ant.types.Path; import org.apache.tools.ant.util.JavaEnvUtils; import org.apache.tools.ant.util.LoaderUtils; import org.apache.tools.ant.util.TeeOutputStream; import org.apache.tools.ant.util.FileUtils; /** * Invokes the ANTLR3 Translator generator on a grammar file. * */ public class ANTLR3 extends Task { private CommandlineJava commandline = new CommandlineJava(); /** the file to process */ private File target = null; /** where to output the result */ private File outputDirectory = null; /** location of token files */ private File libDirectory = null; /** an optional super grammar file */ private File superGrammar; /** depend */ private boolean depend = false; /** fork */ private boolean fork; /** name of output style for messages */ private String messageFormatName; /** optional flag to print out a diagnostic file */ private boolean diagnostic; /** optional flag to add methods */ private boolean trace; /** optional flag to add trace methods to the parser only */ private boolean traceParser; /** optional flag to add trace methods to the lexer only */ private boolean traceLexer; /** working directory */ private File workingdir = null; /** captures ANTLR's output */ private ByteArrayOutputStream bos = new ByteArrayOutputStream(); /** The debug attribute */ private boolean debug; /** The report attribute */ private boolean report; /** The print attribute */ private boolean print; /** The profile attribute */ private boolean profile; /** The nfa attribute */ private boolean nfa; /** The dfa attribute */ private boolean dfa; /** multi threaded analysis */ private boolean multiThreaded; /** collapse incident edges into DFA states */ private boolean nocollapse; /** test lookahead against EBNF block exit branches */ private boolean noprune; /** put tags at start/stop of all templates in output */ private boolean dbgST; /** print AST */ private boolean grammarTree; /** Instance of a utility class to use for file operations. */ private FileUtils fileUtils; /** * Whether to override the default conversion timeout with -Xconversiontimeout nnnn */ private String conversiontimeout; public ANTLR3() { commandline.setVm(JavaEnvUtils.getJreExecutable("java")); commandline.setClassname("org.antlr.Tool"); fileUtils = FileUtils.getFileUtils(); } /** * The grammar file to process. */ public void setTarget(File targetFile) { log("Setting target to: " + targetFile.toString(), Project.MSG_VERBOSE); this.target = targetFile; } /** * The directory to write the generated files to. */ public void setOutputdirectory(File outputDirectoryFile) { log("Setting output directory to: " + outputDirectoryFile.toString(), Project.MSG_VERBOSE); this.outputDirectory = outputDirectoryFile; } /** * The directory to write the generated files to. */ public File getOutputdirectory() { return outputDirectory; } /** * The token files output directory. */ public void setLibdirectory(File libDirectoryFile) { log("Setting lib directory to: " + libDirectoryFile.toString(), Project.MSG_VERBOSE); this.libDirectory = libDirectoryFile; } /** * The output style for messages. */ public void setMessageformat(String name) { log("Setting message-format to: " + name, Project.MSG_VERBOSE); this.messageFormatName = name; } /** * Sets an optional super grammar file * @deprecated */ public void setGlib(File superGrammarFile) { this.superGrammar = superGrammarFile; } /** * Sets a flag to enable ParseView debugging */ public void setDebug(boolean enable) { this.debug = enable; } /** * Sets a flag to enable report statistics */ public void setReport(boolean enable) { this.report = enable; } /** * Sets a flag to print out the grammar without actions */ public void setPrint(boolean enable) { this.print = enable; } /** * Sets a flag to enable profiling */ public void setProfile(boolean enable) { this.profile = enable; } /** * Sets a flag to enable nfa generation */ public void setNfa(boolean enable) { this.nfa = enable; } /** * Sets a flag to enable nfa generation */ public void setDfa(boolean enable) { this.dfa = enable; } /** * Run the analysis multithreaded * @param enable */ public void setMultithreaded(boolean enable) { multiThreaded = enable; } /** * collapse incident edges into DFA states * @param enable */ public void setNocollapse(boolean enable) { nocollapse = enable; } /** * test lookahead against EBNF block exit branches * @param enable */ public void setNoprune(boolean enable) { noprune = enable; } /** * test lookahead against EBNF block exit branches * @param enable */ public void setDbgST(boolean enable) { dbgST = enable; } /** * override the default conversion timeout with -Xconversiontimeout nnnn * @param conversiontimeoutString */ public void setConversiontimeout(String conversiontimeoutString) { log("Setting conversiontimeout to: " + conversiontimeoutString, Project.MSG_VERBOSE); try { int timeout = Integer.valueOf(conversiontimeoutString); this.conversiontimeout = conversiontimeoutString; } catch (NumberFormatException e) { log("Option ConversionTimeOut ignored due to illegal value: '" + conversiontimeoutString + "'", Project.MSG_ERR); } } /** * Set a flag to enable printing of the grammar tree */ public void setGrammartree(boolean enable) { grammarTree = enable; } /** * Set a flag to enable dependency checking by ANTLR itself * The depend option is always used implicitely inside the antlr3 task * @deprecated */ public void setDepend(boolean s) { this.depend = s; } /** * Sets a flag to emit diagnostic text */ public void setDiagnostic(boolean enable) { diagnostic = enable; } /** * If true, enables all tracing. * @deprecated */ public void setTrace(boolean enable) { trace = enable; } /** * If true, enables parser tracing. * @deprecated */ public void setTraceParser(boolean enable) { traceParser = enable; } /** * If true, enables lexer tracing. * @deprecated */ public void setTraceLexer(boolean enable) { traceLexer = enable; } // we are forced to fork ANTLR since there is a call // to System.exit() and there is nothing we can do // right now to avoid this. :-( (SBa) // I'm not removing this method to keep backward compatibility /** * @ant.attribute ignore="true" */ public void setFork(boolean s) { this.fork = s; } /** * The working directory of the process */ public void setDir(File d) { this.workingdir = d; } /** * Adds a classpath to be set * because a directory might be given for Antlr debug. */ public Path createClasspath() { return commandline.createClasspath(getProject()).createPath(); } /** * Adds a new JVM argument. * @return create a new JVM argument so that any argument can be passed to the JVM. * @see #setFork(boolean) */ public Commandline.Argument createJvmarg() { return commandline.createVmArgument(); } /** * Adds the jars or directories containing Antlr and associates. * This should make the forked JVM work without having to * specify it directly. */ @Override public void init() throws BuildException { /* Inquire environment variables */ Map variables = System.getenv(); /* Get value for key "ANTLR_HOME" which should hopefully point to * the directory where the current version of antlr3 is installed */ String antlrHome = variables.get("ANTLR_HOME"); if (antlrHome != null) { /* Environment variable ANTLR_HOME has been defined. * Now add all antlr and stringtemplate libraries to the * classpath */ addAntlrJarsToClasspath(antlrHome + "/lib"); } addClasspathEntry("/antlr/ANTLRGrammarParseBehavior.class", "AntLR2"); addClasspathEntry("/org/antlr/tool/ANTLRParser.class", "AntLR3"); addClasspathEntry("/org/antlr/stringtemplate/StringTemplate.class", "Stringtemplate"); } /** * Search for the given resource and add the directory or archive * that contains it to the classpath. * *

Doesn't work for archives in JDK 1.1 as the URL returned by * getResource doesn't contain the name of the archive.

*/ protected void addClasspathEntry(String resource, String msg) { /* * pre Ant 1.6 this method used to call getClass().getResource * while Ant 1.6 will call ClassLoader.getResource(). * * The difference is that Class.getResource expects a leading * slash for "absolute" resources and will strip it before * delegating to ClassLoader.getResource - so we now have to * emulate Class's behavior. */ if (resource.startsWith("/")) { resource = resource.substring(1); } else { resource = "org/apache/tools/ant/taskdefs/optional/" + resource; } File f = LoaderUtils.getResourceSource(getClass().getClassLoader(), resource); if (f != null) { log("Found via classpath: " + f.getAbsolutePath(), Project.MSG_VERBOSE); createClasspath().setLocation(f); } else { log("Couldn\'t find resource " + resource + " for library " + msg + " in external classpath", Project.MSG_VERBOSE); } } /** * If the environment variable ANTLR_HOME is defined and points * to the installation directory of antlr3 then look for all antlr-*.jar and * stringtemplate-*.jar files in the lib directory and add them * to the classpath. * This feature should make working with eclipse or netbeans projects a * little bit easier. As wildcards are being used for the version part * of the jar-archives it makes the task independent of * new releases. Just let ANTLR_HOME point to the new installation * directory. */ private void addAntlrJarsToClasspath(String antlrLibDir) { String[] includes = {"antlr-*.jar", "stringtemplate-*.jar"}; DirectoryScanner ds = new DirectoryScanner(); ds.setIncludes(includes); ds.setBasedir(new File(antlrLibDir)); ds.setCaseSensitive(true); ds.scan(); String separator = System.getProperty("file.separator"); String[] files = ds.getIncludedFiles(); for (String file : files) { File f = new File(antlrLibDir + separator + file); log("Found via ANTLR_HOME: " + f.getAbsolutePath(), Project.MSG_VERBOSE); createClasspath().setLocation(f); } } @Override public void execute() throws BuildException { validateAttributes(); // Use ANTLR itself to resolve dependencies and decide whether // to invoke ANTLR for compilation if (dependencyCheck()) { populateAttributes(); commandline.createArgument().setValue(target.toString()); log(commandline.describeCommand(), Project.MSG_VERBOSE); int err = 0; try { err = run(commandline.getCommandline(), new LogOutputStream(this, Project.MSG_INFO), new LogOutputStream(this, Project.MSG_WARN)); } catch (IOException e) { throw new BuildException(e, getLocation()); } finally { try { bos.close(); } catch (IOException e) { // ignore } } if (err != 0) { throw new BuildException("ANTLR returned: " + err, getLocation()); } else { Pattern p = Pattern.compile("error\\([0-9]+\\):"); Matcher m = p.matcher(bos.toString()); if (m.find()) { throw new BuildException("ANTLR signaled an error.", getLocation()); } } } else { try { log("All dependencies of grammar file \'" + target.getCanonicalPath() + "\' are up to date.", Project.MSG_VERBOSE); } catch (IOException ex) { log("All dependencies of grammar file \'" + target.toString() + "\' are up to date.", Project.MSG_VERBOSE); } } } /** * A refactored method for populating all the command line arguments based * on the user-specified attributes. */ private void populateAttributes() { commandline.createArgument().setValue("-o"); commandline.createArgument().setValue(outputDirectory.toString()); commandline.createArgument().setValue("-lib"); commandline.createArgument().setValue(libDirectory.toString()); if (superGrammar != null) { log("Option 'glib' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN); } if (diagnostic) { commandline.createArgument().setValue("-diagnostic"); } if (depend) { log("Option 'depend' is implicitely always used by ANTLR v3. Option can safely be omitted!", Project.MSG_WARN); } if (trace) { log("Option 'trace' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN); } if (traceParser) { log("Option 'traceParser' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN); } if (traceLexer) { log("Option 'traceLexer' is not supported by ANTLR v3. Option ignored!", Project.MSG_WARN); } if (debug) { commandline.createArgument().setValue("-debug"); } if (report) { commandline.createArgument().setValue("-report"); } if (print) { commandline.createArgument().setValue("-print"); } if (profile) { commandline.createArgument().setValue("-profile"); } if (messageFormatName != null) { commandline.createArgument().setValue("-message-format"); commandline.createArgument().setValue(messageFormatName); } if (nfa) { commandline.createArgument().setValue("-nfa"); } if (dfa) { commandline.createArgument().setValue("-dfa"); } if (multiThreaded) { commandline.createArgument().setValue("-Xmultithreaded"); } if (nocollapse) { commandline.createArgument().setValue("-Xnocollapse"); } if (noprune) { commandline.createArgument().setValue("-Xnoprune"); } if (dbgST) { commandline.createArgument().setValue("-XdbgST"); } if (conversiontimeout != null) { commandline.createArgument().setValue("-Xconversiontimeout"); commandline.createArgument().setValue(conversiontimeout); } if (grammarTree) { commandline.createArgument().setValue("-Xgrtree"); } } private void validateAttributes() throws BuildException { if (target == null) { throw new BuildException("No target grammar, lexer grammar or tree parser specified!"); } else if (!target.isFile()) { throw new BuildException("Target: " + target + " is not a file!"); } // if no output directory is specified, use the target's directory if (outputDirectory == null) { setOutputdirectory(new File(target.getParent())); } if (!outputDirectory.isDirectory()) { throw new BuildException("Invalid output directory: " + outputDirectory); } if (workingdir != null && !workingdir.isDirectory()) { throw new BuildException("Invalid working directory: " + workingdir); } // if no libDirectory is specified, use the target's directory if (libDirectory == null) { setLibdirectory(new File(target.getParent())); } if (!libDirectory.isDirectory()) { throw new BuildException("Invalid lib directory: " + libDirectory); } } private boolean dependencyCheck() throws BuildException { // using "antlr -o -lib -depend " // to get the list of dependencies CommandlineJava cmdline; try { cmdline = (CommandlineJava) commandline.clone(); } catch (java.lang.CloneNotSupportedException e) { throw new BuildException("Clone of commandline failed: " + e); } cmdline.createArgument().setValue("-depend"); cmdline.createArgument().setValue("-o"); cmdline.createArgument().setValue(outputDirectory.toString()); cmdline.createArgument().setValue("-lib"); cmdline.createArgument().setValue(libDirectory.toString()); cmdline.createArgument().setValue(target.toString()); log(cmdline.describeCommand(), Project.MSG_VERBOSE); // redirect output generated by ANTLR to temporary file Redirector r = new Redirector(this); File f; try { f = File.createTempFile("depend", null, getOutputdirectory()); f.deleteOnExit(); log("Write dependencies for '" + target.toString() + "' to file '" + f.getCanonicalPath() + "'", Project.MSG_VERBOSE); r.setOutput(f); r.setAlwaysLog(false); r.createStreams(); } catch (IOException e) { throw new BuildException("Redirection of output failed: " + e); } // execute antlr -depend ... int err = 0; try { err = run(cmdline.getCommandline(), r.getOutputStream(), null); } catch (IOException e) { try { r.complete(); log("Redirection of output terminated.", Project.MSG_VERBOSE); } catch (IOException ex) { log("Termination of output redirection failed: " + ex, Project.MSG_ERR); } throw new BuildException(e, getLocation()); } finally { try { bos.close(); } catch (IOException e) { // ignore } } try { r.complete(); log("Redirection of output terminated.", Project.MSG_VERBOSE); } catch (IOException e) { log("Termination of output redirection failed: " + e, Project.MSG_ERR); } if (err != 0) { if (f.exists()) { f.delete(); } if (cmdline.getClasspath() == null) { log("Antlr libraries not found in external classpath or embedded classpath statement ", Project.MSG_ERR); } log("Dependency check failed. ANTLR returned: " + err, Project.MSG_ERR); return true; } else { Pattern p = Pattern.compile("error\\([0-9]+\\):"); Matcher m = p.matcher(bos.toString()); if (m.find()) { if (f.exists()) { f.delete(); } // On error always recompile return true; } } boolean compile = false; // open temporary file BufferedReader in = null; try { in = new BufferedReader(new FileReader(f)); } catch (IOException e) { try { if (in != null) { in.close(); } } catch (IOException ex) { throw new BuildException("Could not close file\'" + f.toString() + "\'."); } if (f.exists()) { f.delete(); } try { throw new BuildException("Could not open \'" + f.getCanonicalPath() + "\' for reading."); } catch (IOException ex) { throw new BuildException("Could not open \'" + f.toString() + "\' for reading."); } } // evaluate dependencies in temporary file String s; try { while ((s = in.readLine()) != null) { String a; String b; // As the separator string in lines emitted by the depend option // is either " : " or ": " trim is invoked for the first file name of a line int to = s.indexOf(": "); if (to >= 0) { a = s.substring(0, to).trim(); File lhs = new File(a); if (!lhs.isFile()) { log("File '" + a + "' is not a regular file", Project.MSG_VERBOSE); String name = lhs.getName(); String[] parts = splitRightHandSide(name, "\\u002E"); if (parts.length <= 1) { a += ".java"; lhs = new File(a); if (lhs.isFile()) { log("File '" + a + "' is a regular file last modified at " + lhs.lastModified(), Project.MSG_VERBOSE); } } } b = s.substring(to + ": ".length()); String[] names = splitRightHandSide(b, ", ?"); File aFile = new File(a); for (String name : names) { File bFile = new File(name); log("File '" + a + "' depends on file '" + name + "'", Project.MSG_VERBOSE); log("File '" + a + "' modified at " + aFile.lastModified(), Project.MSG_VERBOSE); log("File '" + name + "' modified at " + bFile.lastModified(), Project.MSG_VERBOSE); if (fileUtils.isUpToDate(aFile, bFile)) { log("Compiling " + target + " as '" + name + "' is newer than '" + a + "'", Project.MSG_VERBOSE); // Feeling not quite comfortable with touching the file fileUtils.setFileLastModified(aFile, -1); log("Touching file '" + a + "'", Project.MSG_VERBOSE); compile = true; break; } } if (compile) { break; } } } in.close(); } catch (IOException e) { if (f.exists()) { f.delete(); } throw new BuildException("Error reading file '" + f.toString() + "'"); } if (f.exists()) { f.delete(); } return compile; } private String[] splitRightHandSide(String fileNames, String pattern) { String[] names = fileNames.split(pattern); for (String name : names) { log("Split right hand side '" + name + "'", Project.MSG_VERBOSE); } return names; } /** execute in a forked VM */ private int run(String[] command, OutputStream out, OutputStream err) throws IOException { PumpStreamHandler psh; if (err == null) { psh = new PumpStreamHandler(out, bos); } else { psh = new PumpStreamHandler(out, new TeeOutputStream(err, bos)); } Execute exe = new Execute(psh, null); exe.setAntRun(getProject()); if (workingdir != null) { exe.setWorkingDirectory(workingdir); } exe.setCommandline(command); return exe.execute(); } }