/*
 * [The "BSD license"]
 *  Copyright (c) 2010 Terence Parr
 *  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.
 */
package org.antlr;

import org.antlr.analysis.*;
import org.antlr.codegen.CodeGenerator;
import org.antlr.misc.Graph;
import org.antlr.runtime.misc.Stats;
import org.antlr.tool.*;
import org.stringtemplate.v4.STGroup;

import java.io.*;
import java.util.*;

/** The main ANTLR entry point.  Read a grammar and generate a parser. */
public class Tool {

    public final Properties antlrSettings = new Properties();

	public final String VERSION;
	{
		String version = Tool.class.getPackage().getImplementationVersion();
		VERSION = version != null ? version : "3.x";
	}

    public static final String UNINITIALIZED_DIR = "<unset-dir>";
    private List<String> grammarFileNames = new ArrayList<String>();
    private boolean generate_NFA_dot = false;
    private boolean generate_DFA_dot = false;
    private String outputDirectory = ".";
    private boolean haveOutputDir = false;
    private String inputDirectory = null;
    private String parentGrammarDirectory;
    private String grammarOutputDirectory;
    private boolean haveInputDir = false;
    private String libDirectory = ".";
    private boolean debug = false;
    private boolean trace = false;
    private boolean profile = false;
    private boolean report = false;
    private boolean printGrammar = false;
    private boolean depend = false;
    private boolean forceAllFilesToOutputDir = false;
    private boolean forceRelativeOutput = false;
    protected boolean deleteTempLexer = true;
    private boolean verbose = false;
    /** Don't process grammar file if generated files are newer than grammar */
    private boolean make = false;
    private boolean showBanner = true;
	private static boolean exitNow = false;
	private static boolean return_dont_exit = false;


	public String forcedLanguageOption; // -language L on command line

    // The internal options are for my use on the command line during dev
    //
    public static boolean internalOption_PrintGrammarTree = false;
    public static boolean internalOption_PrintDFA = false;
    public static boolean internalOption_ShowNFAConfigsInDFA = false;
    public static boolean internalOption_watchNFAConversion = false;

    /**
     * A list of dependency generators that are accumulated aaaas (and if) the
     * tool is required to sort the provided grammars into build dependency order.
    protected Map<String, BuildDependencyGenerator> buildDependencyGenerators;
     */

    public static void main(String[] args) {
        Tool antlr = new Tool(args);

        if (!exitNow) {
            antlr.process();
			if ( return_dont_exit ) return;
            if (ErrorManager.getNumErrors() > 0) {
                System.exit(1);
            }
            System.exit(0);
        }
    }

    /**
     * Load the properties file org/antlr/antlr.properties and populate any
     * variables that must be initialized from it, such as the version of ANTLR.
     */
    private void loadResources() {
        InputStream in;
        in = this.getClass().getResourceAsStream("antlr.properties");

        // If we found the resource, then load it, otherwise revert to the
        // defaults.
        //
        if (in != null) {
            try {
                // Load the resources into the map
                //
                antlrSettings.load(in);

                // Set any variables that we need to populate from the resources
                //
//                VERSION = antlrSettings.getProperty("antlr.version");
            } catch (Exception e) {
                // Do nothing, just leave the defaults in place
            }
        }
    }

    public Tool() {
        loadResources();
    }

	@SuppressWarnings("OverridableMethodCallInConstructor")
    public Tool(String[] args) {
        loadResources();

        // Set all the options and pick up all the named grammar files
        processArgs(args);
    }

    public void processArgs(String[] args) {

        if (isVerbose()) {
            ErrorManager.info("ANTLR Parser Generator  Version " + VERSION);
            showBanner = false;
        }

        if (args == null || args.length == 0) {
            help();
            return;
        }
        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("-o") || args[i].equals("-fo") || args[i].equals("-ro")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing output directory with " + args[i] + " option; ignoring");
                }
                else {
                    if (args[i].equals("-fo")) { // force output into dir
                        setForceAllFilesToOutputDir(true);
                    }
					else if (args[i].equals("-ro")) {
						setForceRelativeOutput(true);
					}
                    i++;
                    outputDirectory = args[i];
                    if (outputDirectory.endsWith("/") ||
                        outputDirectory.endsWith("\\")) {
                        outputDirectory =
                            outputDirectory.substring(0, getOutputDirectory().length() - 1);
                    }
                    File outDir = new File(outputDirectory);
                    haveOutputDir = true;
                    if (outDir.exists() && !outDir.isDirectory()) {
                        ErrorManager.error(ErrorManager.MSG_OUTPUT_DIR_IS_FILE, outputDirectory);
                        setLibDirectory(".");
                    }
                }
            }
			else if (args[i].equals("-in")) {
				if (i + 1 >= args.length) {
					System.err.println("missing input directory with -in option; ignoring");
				}
				else {
					i++;
					String directory = args[i];
					if (directory.endsWith("/") || directory.endsWith("\\")) {
						directory = directory.substring(0, directory.length() - 1);
					}

					if (!new File(directory).isDirectory()) {
						System.err.println("the specified input directory does not exist");
						continue;
					}

					setInputDirectory(args[i]);
				}
			}
			else if (args[i].equals("-lib")) {
				if (i + 1 >= args.length) {
					System.err.println("missing library directory with -lib option; ignoring");
				}
				else {
					i++;
					setLibDirectory(args[i]);
					if (getLibraryDirectory().endsWith("/") ||
						getLibraryDirectory().endsWith("\\")) {
						setLibDirectory(getLibraryDirectory().substring(0, getLibraryDirectory().length() - 1));
					}
					File outDir = new File(getLibraryDirectory());
					if (!outDir.exists()) {
						ErrorManager.error(ErrorManager.MSG_DIR_NOT_FOUND, getLibraryDirectory());
						setLibDirectory(".");
					}
				}
			}
			else if (args[i].equals("-language")) {
				if (i + 1 >= args.length) {
					System.err.println("missing language name; ignoring");
				}
				else {
					i++;
					forcedLanguageOption = args[i];
				}
			}
            else if (args[i].equals("-nfa")) {
                setGenerate_NFA_dot(true);
            }
            else if (args[i].equals("-dfa")) {
                setGenerate_DFA_dot(true);
            }
            else if (args[i].equals("-debug")) {
                setDebug(true);
            }
            else if (args[i].equals("-trace")) {
                setTrace(true);
            }
            else if (args[i].equals("-report")) {
                setReport(true);
            }
            else if (args[i].equals("-profile")) {
                setProfile(true);
            }
            else if (args[i].equals("-print")) {
                setPrintGrammar(true);
            }
            else if (args[i].equals("-depend")) {
                setDepend(true);
            }
            else if (args[i].equals("-verbose")) {
                setVerbose(true);
            }
            else if (args[i].equals("-version")) {
                version();
                exitNow = true;
            }
            else if (args[i].equals("-make")) {
                setMake(true);
            }
            else if (args[i].equals("-message-format")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing output format with -message-format option; using default");
                }
                else {
                    i++;
                    ErrorManager.setFormat(args[i]);
                }
            }
            else if (args[i].equals("-Xgrtree")) {
                internalOption_PrintGrammarTree = true; // print grammar tree
            }
            else if (args[i].equals("-Xdfa")) {
                internalOption_PrintDFA = true;
            }
            else if (args[i].equals("-Xnoprune")) {
                DFAOptimizer.PRUNE_EBNF_EXIT_BRANCHES = false;
            }
            else if (args[i].equals("-Xnocollapse")) {
                DFAOptimizer.COLLAPSE_ALL_PARALLEL_EDGES = false;
            }
            else if (args[i].equals("-Xdbgconversion")) {
                NFAToDFAConverter.debug = true;
            }
            else if (args[i].equals("-Xmultithreaded")) {
                NFAToDFAConverter.SINGLE_THREADED_NFA_CONVERSION = false;
            }
            else if (args[i].equals("-Xnomergestopstates")) {
                DFAOptimizer.MERGE_STOP_STATES = false;
            }
            else if (args[i].equals("-Xdfaverbose")) {
                internalOption_ShowNFAConfigsInDFA = true;
            }
            else if (args[i].equals("-Xwatchconversion")) {
                internalOption_watchNFAConversion = true;
            }
            else if (args[i].equals("-XdbgST")) {
                CodeGenerator.LAUNCH_ST_INSPECTOR = true;
				STGroup.trackCreationEvents = true;
				return_dont_exit = true;
            }
            else if (args[i].equals("-Xmaxinlinedfastates")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing max inline dfa states -Xmaxinlinedfastates option; ignoring");
                }
                else {
                    i++;
                    CodeGenerator.MAX_ACYCLIC_DFA_STATES_INLINE = Integer.parseInt(args[i]);
                }
            }
            else if (args[i].equals("-Xmaxswitchcaselabels")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing max switch case labels -Xmaxswitchcaselabels option; ignoring");
                }
                else {
                    i++;
                    CodeGenerator.MAX_SWITCH_CASE_LABELS = Integer.parseInt(args[i]);
                }
            }
            else if (args[i].equals("-Xminswitchalts")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing min switch alternatives -Xminswitchalts option; ignoring");
                }
                else {
                    i++;
                    CodeGenerator.MIN_SWITCH_ALTS = Integer.parseInt(args[i]);
                }
            }
            else if (args[i].equals("-Xm")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing max recursion with -Xm option; ignoring");
                }
                else {
                    i++;
                    NFAContext.MAX_SAME_RULE_INVOCATIONS_PER_NFA_CONFIG_STACK = Integer.parseInt(args[i]);
                }
            }
            else if (args[i].equals("-Xmaxdfaedges")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing max number of edges with -Xmaxdfaedges option; ignoring");
                }
                else {
                    i++;
                    DFA.MAX_STATE_TRANSITIONS_FOR_TABLE = Integer.parseInt(args[i]);
                }
            }
            else if (args[i].equals("-Xconversiontimeout")) {
                if (i + 1 >= args.length) {
                    System.err.println("missing max time in ms -Xconversiontimeout option; ignoring");
                }
                else {
                    i++;
                    DFA.MAX_TIME_PER_DFA_CREATION = Integer.parseInt(args[i]);
                }
            }
			else if (args[i].equals("-Xnfastates")) {
				DecisionProbe.verbose = true;
			}
			else if (args[i].equals("-Xsavelexer")) {
				deleteTempLexer = false;
			}
            else if (args[i].equals("-X")) {
                Xhelp();
            }
            else {
                if (args[i].charAt(0) != '-') {
                    // Must be the grammar file
                    addGrammarFile(args[i]);
                }
            }
        }
    }

    /*
    protected void checkForInvalidArguments(String[] args, BitSet cmdLineArgValid) {
    // check for invalid command line args
    for (int a = 0; a < args.length; a++) {
    if (!cmdLineArgValid.member(a)) {
    System.err.println("invalid command-line argument: " + args[a] + "; ignored");
    }
    }
    }
     */

    /**
     * Checks to see if the list of outputFiles all exist, and have
     * last-modified timestamps which are later than the last-modified
     * timestamp of all the grammar files involved in build the output
     * (imports must be checked). If these conditions hold, the method
     * returns false, otherwise, it returns true.
     *
     * @param grammarFileName The grammar file we are checking
     */
    public boolean buildRequired(String grammarFileName)
        throws IOException
    {
        BuildDependencyGenerator bd =
            new BuildDependencyGenerator(this, grammarFileName);

        List<File> outputFiles = bd.getGeneratedFileList();
        List<File> inputFiles = bd.getDependenciesFileList();
        // Note that input directory must be set to use buildRequired
        File grammarFile;
        if (haveInputDir) {
            grammarFile = new File(inputDirectory, grammarFileName);
        }
        else {
            grammarFile = new File(grammarFileName);
        }
        long grammarLastModified = grammarFile.lastModified();
        for (File outputFile : outputFiles) {
            if (!outputFile.exists() || grammarLastModified > outputFile.lastModified()) {
                // One of the output files does not exist or is out of date, so we must build it
				if (isVerbose()) {
					if (!outputFile.exists()) {
						System.out.println("Output file " + outputFile + " does not exist: must build " + grammarFile);
					}
					else {
						System.out.println("Output file " + outputFile + " is not up-to-date: must build " + grammarFile);
					}
				}

                return true;
            }
            // Check all of the imported grammars and see if any of these are younger
            // than any of the output files.
            if (inputFiles != null) {
                for (File inputFile : inputFiles) {

                    if (inputFile.lastModified() > outputFile.lastModified()) {
                        // One of the imported grammar files has been updated so we must build
						if (isVerbose()) {
							System.out.println("Input file " + inputFile + " is newer than output: must rebuild " + grammarFile);
						}

                        return true;
                    }
                }
            }
        }
        if (isVerbose()) {
            System.out.println("Grammar " + grammarFile + " is up to date - build skipped");
        }
        return false;
    }

    public void process() {
        boolean exceptionWhenWritingLexerFile = false;
        String lexerGrammarFileName;		// necessary at this scope to have access in the catch below

        // Have to be tricky here when Maven or build tools call in and must new Tool()
        // before setting options. The banner won't display that way!
        if (isVerbose() && showBanner) {
            ErrorManager.info("ANTLR Parser Generator  Version " + VERSION);
            showBanner = false;
        }

        try {
            sortGrammarFiles(); // update grammarFileNames
        }
        catch (Exception e) {
            ErrorManager.error(ErrorManager.MSG_INTERNAL_ERROR,e);
        }
        catch (Error e) {
            ErrorManager.error(ErrorManager.MSG_INTERNAL_ERROR, e);
        }

        for (String grammarFileName : grammarFileNames) {
            // If we are in make mode (to support build tools like Maven) and the
            // file is already up to date, then we do not build it (and in verbose mode
            // we will say so).
            if (make) {
                try {
                    if ( !buildRequired(grammarFileName) ) continue;
                }
                catch (Exception e) {
                    ErrorManager.error(ErrorManager.MSG_INTERNAL_ERROR,e);
                }
            }

            if (isVerbose() && !isDepend()) {
                System.out.println(grammarFileName);
            }
            try {
                if (isDepend()) {
                    BuildDependencyGenerator dep =
                        new BuildDependencyGenerator(this, grammarFileName);
                    /*
                    List outputFiles = dep.getGeneratedFileList();
                    List dependents = dep.getDependenciesFileList();
                    System.out.println("output: "+outputFiles);
                    System.out.println("dependents: "+dependents);
                     */
                    System.out.println(dep.getDependencies().render());
                    continue;
                }

                Grammar rootGrammar = getRootGrammar(grammarFileName);
                // we now have all grammars read in as ASTs
                // (i.e., root and all delegates)
				rootGrammar.composite.assignTokenTypes();
				//rootGrammar.composite.translateLeftRecursiveRules();
				rootGrammar.addRulesForSyntacticPredicates();
				rootGrammar.composite.defineGrammarSymbols();
                rootGrammar.composite.createNFAs();

                generateRecognizer(rootGrammar);

                if (isPrintGrammar()) {
                    rootGrammar.printGrammar(System.out);
                }

                if (isReport()) {
					GrammarReport2 greport = new GrammarReport2(rootGrammar);
					System.out.print(greport.toString());
//                    GrammarReport greport = new GrammarReport(rootGrammar);
//                    System.out.println(greport.toString());
//                    // print out a backtracking report too (that is not encoded into log)
//                    System.out.println(greport.getBacktrackingReport());
                }
                if (isProfile()) {
                    GrammarReport greport = new GrammarReport(rootGrammar);
                    Stats.writeReport(GrammarReport.GRAMMAR_STATS_FILENAME,
                                      greport.toNotifyString());
                }

                // now handle the lexer if one was created for a merged spec
                String lexerGrammarStr = rootGrammar.getLexerGrammar();
                //System.out.println("lexer rootGrammar:\n"+lexerGrammarStr);
                if (rootGrammar.type == Grammar.COMBINED && lexerGrammarStr != null) {
                    lexerGrammarFileName = rootGrammar.getImplicitlyGeneratedLexerFileName();
                    try {
                        Writer w = getOutputFile(rootGrammar, lexerGrammarFileName);
                        w.write(lexerGrammarStr);
                        w.close();
                    }
                    catch (IOException e) {
                        // emit different error message when creating the implicit lexer fails
                        // due to write permission error
                        exceptionWhenWritingLexerFile = true;
                        throw e;
                    }
                    try {
                        StringReader sr = new StringReader(lexerGrammarStr);
                        Grammar lexerGrammar = new Grammar(this);
                        lexerGrammar.composite.watchNFAConversion = internalOption_watchNFAConversion;
                        lexerGrammar.implicitLexer = true;
                        //lexerGrammar.setTool(this);
                        File lexerGrammarFullFile =
                            new File(getFileDirectory(lexerGrammarFileName), lexerGrammarFileName);
                        lexerGrammar.setFileName(lexerGrammarFullFile.toString());

                        lexerGrammar.importTokenVocabulary(rootGrammar);
                        lexerGrammar.parseAndBuildAST(sr);

                        sr.close();

                        lexerGrammar.composite.assignTokenTypes();
						lexerGrammar.addRulesForSyntacticPredicates();
                        lexerGrammar.composite.defineGrammarSymbols();
                        lexerGrammar.composite.createNFAs();

                        generateRecognizer(lexerGrammar);
                    }
                    finally {
                        // make sure we clean up
                        if (deleteTempLexer) {
                            File outputDir = getOutputDirectory(lexerGrammarFileName);
                            File outputFile = new File(outputDir, lexerGrammarFileName);
                            outputFile.delete();
                        }
                    }
                }
            }
            catch (IOException e) {
                if (exceptionWhenWritingLexerFile) {
                    ErrorManager.error(ErrorManager.MSG_CANNOT_WRITE_FILE, e);
                }
                else {
                    ErrorManager.error(ErrorManager.MSG_CANNOT_OPEN_FILE,
                                       grammarFileName);
                }
            }
            catch (Exception e) {
                ErrorManager.error(ErrorManager.MSG_INTERNAL_ERROR, grammarFileName, e);
            }
            /*
           finally {
           System.out.println("creates="+ Interval.creates);
           System.out.println("hits="+ Interval.hits);
           System.out.println("misses="+ Interval.misses);
           System.out.println("outOfRange="+ Interval.outOfRange);
           }
            */
        }
    }

    public void sortGrammarFiles() throws IOException {
        //System.out.println("Grammar names "+getGrammarFileNames());
        Graph<String> g = new Graph<String>();
        List<String> missingFiles = new ArrayList<String>();
        for (String gfile : grammarFileNames) {
            try {
                GrammarSpelunker grammar = new GrammarSpelunker(inputDirectory, gfile);
                grammar.parse();
                String vocabName = grammar.getTokenVocab();
                String grammarName = grammar.getGrammarName();
                // Make all grammars depend on any tokenVocab options
                if ( vocabName!=null ) g.addEdge(gfile, vocabName+CodeGenerator.VOCAB_FILE_EXTENSION);
                // Make all generated tokens files depend on their grammars
                g.addEdge(grammarName+CodeGenerator.VOCAB_FILE_EXTENSION, gfile);
            }
            catch (FileNotFoundException fnfe) {
                ErrorManager.error(ErrorManager.MSG_CANNOT_OPEN_FILE, gfile);
                missingFiles.add(gfile);
            }
        }
        List<String> sorted = g.sort();
        //System.out.println("sorted="+sorted);
        grammarFileNames.clear(); // wipe so we can give new ordered list
        for (int i = 0; i < sorted.size(); i++) {
            String f = sorted.get(i);
            if ( missingFiles.contains(f) ) continue;
            if ( !(f.endsWith(".g") || f.endsWith(".g3")) ) continue;
            grammarFileNames.add(f);
        }
        //System.out.println("new grammars="+grammarFileNames);
    }

    /** Get a grammar mentioned on the command-line and any delegates */
    public Grammar getRootGrammar(String grammarFileName)
        throws IOException
    {
        //ST.setLintMode(true);
        // grammars mentioned on command line are either roots or single grammars.
        // create the necessary composite in case it's got delegates; even
        // single grammar needs it to get token types.
        CompositeGrammar composite = new CompositeGrammar();
        Grammar grammar = new Grammar(this, grammarFileName, composite);
        composite.setDelegationRoot(grammar);
        FileReader fr;
        File f;

        if (haveInputDir) {
            f = new File(inputDirectory, grammarFileName);
        }
        else {
            f = new File(grammarFileName);
        }

        // Store the location of this grammar as if we import files, we can then
        // search for imports in the same location as the original grammar as well as in
        // the lib directory.
        //
        parentGrammarDirectory = f.getParent();

        if (grammarFileName.lastIndexOf(File.separatorChar) == -1) {
            grammarOutputDirectory = ".";
        }
        else {
            grammarOutputDirectory = grammarFileName.substring(0, grammarFileName.lastIndexOf(File.separatorChar));
        }
        fr = new FileReader(f);
        BufferedReader br = new BufferedReader(fr);
        grammar.parseAndBuildAST(br);
        composite.watchNFAConversion = internalOption_watchNFAConversion;
        br.close();
        fr.close();
        return grammar;
    }

    /** Create NFA, DFA and generate code for grammar.
     *  Create NFA for any delegates first.  Once all NFA are created,
     *  it's ok to create DFA, which must check for left-recursion.  That check
     *  is done by walking the full NFA, which therefore must be complete.
     *  After all NFA, comes DFA conversion for root grammar then code gen for
     *  root grammar.  DFA and code gen for delegates comes next.
     */
    protected void generateRecognizer(Grammar grammar) {
        String language = (String) grammar.getOption("language");
        if (language != null) {
            CodeGenerator generator = new CodeGenerator(this, grammar, language);
            grammar.setCodeGenerator(generator);
            generator.setDebug(isDebug());
            generator.setProfile(isProfile());
            generator.setTrace(isTrace());

            // generate NFA early in case of crash later (for debugging)
            if (isGenerate_NFA_dot()) {
                generateNFAs(grammar);
            }

            // GENERATE CODE
            generator.genRecognizer();

            if (isGenerate_DFA_dot()) {
                generateDFAs(grammar);
            }

            List<Grammar> delegates = grammar.getDirectDelegates();
            for (int i = 0; delegates != null && i < delegates.size(); i++) {
                Grammar delegate = delegates.get(i);
                if (delegate != grammar) { // already processing this one
                    generateRecognizer(delegate);
                }
            }
        }
    }

    public void generateDFAs(Grammar g) {
        for (int d = 1; d <= g.getNumberOfDecisions(); d++) {
            DFA dfa = g.getLookaheadDFA(d);
            if (dfa == null) {
                continue; // not there for some reason, ignore
            }
            DOTGenerator dotGenerator = new DOTGenerator(g);
            String dot = dotGenerator.getDOT(dfa.startState);
            String dotFileName = g.name + "." + "dec-" + d;
            if (g.implicitLexer) {
                dotFileName = g.name + Grammar.grammarTypeToFileNameSuffix[g.type] + "." + "dec-" + d;
            }
            try {
                writeDOTFile(g, dotFileName, dot);
            } catch (IOException ioe) {
                ErrorManager.error(ErrorManager.MSG_CANNOT_GEN_DOT_FILE,
                                   dotFileName,
                                   ioe);
            }
        }
    }

    protected void generateNFAs(Grammar g) {
        DOTGenerator dotGenerator = new DOTGenerator(g);
        Collection<Rule> rules = new HashSet<Rule>(g.getAllImportedRules());
        rules.addAll(g.getRules());

        for (Rule r : rules) {
            try {
                String dot = dotGenerator.getDOT(r.startState);
                if (dot != null) {
                    writeDOTFile(g, r, dot);
                }
            } catch (IOException ioe) {
                ErrorManager.error(ErrorManager.MSG_CANNOT_WRITE_FILE, ioe);
            }
        }
    }

    protected void writeDOTFile(Grammar g, Rule r, String dot) throws IOException {
        writeDOTFile(g, r.grammar.name + "." + r.name, dot);
    }

    protected void writeDOTFile(Grammar g, String name, String dot) throws IOException {
        Writer fw = getOutputFile(g, name + ".dot");
        fw.write(dot);
        fw.close();
    }

    private static void version() {
        ErrorManager.info("ANTLR Parser Generator  Version " + new Tool().VERSION);
    }

    private static void help() {
        ErrorManager.info("ANTLR Parser Generator  Version " + new Tool().VERSION);
        System.err.println("usage: java org.antlr.Tool [args] file.g [file2.g file3.g ...]");
        System.err.println("  -o outputDir          specify output directory where all output is generated");
        System.err.println("  -fo outputDir         same as -o but force even files with relative paths to dir");
        System.err.println("  -lib dir              specify location of token files");
        System.err.println("  -depend               generate file dependencies");
        System.err.println("  -report               print out a report about the grammar(s) processed");
        System.err.println("  -print                print out the grammar without actions");
        System.err.println("  -debug                generate a parser that emits debugging events");
		System.err.println("  -profile              generate a parser that computes profiling information");
		System.err.println("  -trace                generate a recognizer that traces rule entry/exit");
        System.err.println("  -nfa                  generate an NFA for each rule");
        System.err.println("  -dfa                  generate a DFA for each decision point");
        System.err.println("  -message-format name  specify output style for messages");
        System.err.println("  -verbose              generate ANTLR version and other information");
        System.err.println("  -make                 only build if generated files older than grammar");
		System.err.println("  -version              print the version of ANTLR and exit.");
		System.err.println("  -language L           override language grammar option; generate L");
        System.err.println("  -X                    display extended argument list");
    }

    private static void Xhelp() {
        ErrorManager.info("ANTLR Parser Generator  Version " + new Tool().VERSION);
        System.err.println("  -Xgrtree                print the grammar AST");
        System.err.println("  -Xdfa                   print DFA as text ");
        System.err.println("  -Xnoprune               test lookahead against EBNF block exit branches");
        System.err.println("  -Xnocollapse            collapse incident edges into DFA states");
		System.err.println("  -Xdbgconversion         dump lots of info during NFA conversion");
		System.err.println("  -Xconversiontimeout     use to restrict NFA conversion exponentiality");
        System.err.println("  -Xmultithreaded         run the analysis in 2 threads");
        System.err.println("  -Xnomergestopstates     do not merge stop states");
        System.err.println("  -Xdfaverbose            generate DFA states in DOT with NFA configs");
        System.err.println("  -Xwatchconversion       print a message for each NFA before converting");
        System.err.println("  -XdbgST                 put tags at start/stop of all templates in output");
        System.err.println("  -Xnfastates             for nondeterminisms, list NFA states for each path");
        System.err.println("  -Xm m                   max number of rule invocations during conversion           [" + NFAContext.MAX_SAME_RULE_INVOCATIONS_PER_NFA_CONFIG_STACK + "]");
        System.err.println("  -Xmaxdfaedges m         max \"comfortable\" number of edges for single DFA state     [" + DFA.MAX_STATE_TRANSITIONS_FOR_TABLE + "]");
        System.err.println("  -Xmaxinlinedfastates m  max DFA states before table used rather than inlining      [" + CodeGenerator.MADSI_DEFAULT +"]");
        System.err.println("  -Xmaxswitchcaselabels m don't generate switch() statements for dfas bigger  than m [" + CodeGenerator.MSCL_DEFAULT +"]");
		System.err.println("  -Xminswitchalts m       don't generate switch() statements for dfas smaller than m [" + CodeGenerator.MSA_DEFAULT + "]");
		System.err.println("  -Xsavelexer             don't delete temporary lexers generated from combined grammars");
    }

    /**
     * Set the threshold of case labels beyond which ANTLR will not instruct the target template
     * to generate switch() { case xxx: ...
     *
     * @param maxSwitchCaseLabels Maximum number of case lables that ANTLR should allow the target code
     */
    public void setMaxSwitchCaseLabels(int maxSwitchCaseLabels) {
        CodeGenerator.MAX_SWITCH_CASE_LABELS = maxSwitchCaseLabels;
    }

    /**
     * Set the threshold of the number alts, below which ANTLR will not instruct the target
     * template to use a switch statement.
     *
     * @param minSwitchAlts the minimum number of alts required to use a switch staement
     */
    public void setMinSwitchAlts(int minSwitchAlts) {
        CodeGenerator.MIN_SWITCH_ALTS = minSwitchAlts;
    }

    /**
     * Set the location (base directory) where output files should be produced
     * by the ANTLR tool.
     * @param outputDirectory
     */
    public void setOutputDirectory(String outputDirectory) {
        haveOutputDir = true;
        this.outputDirectory = outputDirectory;
    }

    /**
     * Used by build tools to force the output files to always be
     * relative to the base output directory, even though the tool
     * had to set the output directory to an absolute path as it
     * cannot rely on the workign directory like command line invocation
     * can.
     *
     * @param forceRelativeOutput true if output files hould always be relative to base output directory
     */
    public void setForceRelativeOutput(boolean forceRelativeOutput) {
        this.forceRelativeOutput = forceRelativeOutput;
    }

    /**
     * Set the base location of input files. Normally (when the tool is
     * invoked from the command line), the inputDirectory is not set, but
     * for build tools such as Maven, we need to be able to locate the input
     * files relative to the base, as the working directory could be anywhere and
     * changing workig directories is not a valid concept for JVMs because of threading and
     * so on. Setting the directory just means that the getFileDirectory() method will
     * try to open files relative to this input directory.
     *
     * @param inputDirectory Input source base directory
     */
    public void setInputDirectory(String inputDirectory) {
        this.inputDirectory = inputDirectory;
        haveInputDir = true;
    }

    /** This method is used by all code generators to create new output
     *  files. If the outputDir set by -o is not present it will be created.
     *  The final filename is sensitive to the output directory and
     *  the directory where the grammar file was found.  If -o is /tmp
     *  and the original grammar file was foo/t.g then output files
     *  go in /tmp/foo.
     *
     *  The output dir -o spec takes precedence if it's absolute.
     *  E.g., if the grammar file dir is absolute the output dir is given
     *  precendence. "-o /tmp /usr/lib/t.g" results in "/tmp/T.java" as
     *  output (assuming t.g holds T.java).
     *
     *  If no -o is specified, then just write to the directory where the
     *  grammar file was found.
     *
     *  If outputDirectory==null then write a String.
     */
    public Writer getOutputFile(Grammar g, String fileName) throws IOException {
        if (getOutputDirectory() == null) {
            return new StringWriter();
        }
        // output directory is a function of where the grammar file lives
        // for subdir/T.g, you get subdir here.  Well, depends on -o etc...
        // But, if this is a .tokens file, then we force the output to
        // be the base output directory (or current directory if there is not a -o)
        //
        File outputDir;
        if (fileName.endsWith(CodeGenerator.VOCAB_FILE_EXTENSION)) {
            if (haveOutputDir) {
                outputDir = new File(getOutputDirectory());
            }
            else {
                outputDir = new File(".");
            }
        }
        else {
            outputDir = getOutputDirectory(g.getFileName());
        }
        File outputFile = new File(outputDir, fileName);

        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }
        FileWriter fw = new FileWriter(outputFile);
        return new BufferedWriter(fw);
    }

    /**
     * Return the location where ANTLR will generate output files for a given file. This is a
     * base directory and output files will be relative to here in some cases
     * such as when -o option is used and input files are given relative
     * to the input directory.
     *
     * @param fileNameWithPath path to input source
     */
    public File getOutputDirectory(String fileNameWithPath) {

        File outputDir;
        String fileDirectory;

        // Some files are given to us without a PATH but should should
        // still be written to the output directory in the relative path of
        // the output directory. The file directory is either the set of sub directories
        // or just or the relative path recorded for the parent grammar. This means
        // that when we write the tokens files, or the .java files for imported grammars
        // taht we will write them in the correct place.
        //
        if (fileNameWithPath.lastIndexOf(File.separatorChar) == -1) {

            // No path is included in the file name, so make the file
            // directory the same as the parent grammar (which might sitll be just ""
            // but when it is not, we will write the file in the correct place.
            //
            fileDirectory = grammarOutputDirectory;

        }
        else {
            fileDirectory = fileNameWithPath.substring(0, fileNameWithPath.lastIndexOf(File.separatorChar));
        }
        if (haveOutputDir) {
            // -o /tmp /var/lib/t.g => /tmp/T.java
            // -o subdir/output /usr/lib/t.g => subdir/output/T.java
            // -o . /usr/lib/t.g => ./T.java
            if ((fileDirectory != null && !forceRelativeOutput) &&
                (new File(fileDirectory).isAbsolute() ||
                 fileDirectory.startsWith("~")) || // isAbsolute doesn't count this :(
                isForceAllFilesToOutputDir()) {
                // somebody set the dir, it takes precendence; write new file there
                outputDir = new File(getOutputDirectory());
            }
            else {
                // -o /tmp subdir/t.g => /tmp/subdir/t.g
                if (fileDirectory != null) {
                    outputDir = new File(getOutputDirectory(), fileDirectory);
                }
                else {
                    outputDir = new File(getOutputDirectory());
                }
            }
        }
        else {
            // they didn't specify a -o dir so just write to location
            // where grammar is, absolute or relative, this will only happen
            // with command line invocation as build tools will always
            // supply an output directory.
            //
            outputDir = new File(fileDirectory);
        }
        return outputDir;
    }

    /**
     * Name a file from the -lib dir.  Imported grammars and .tokens files
     *
     * If we do not locate the file in the library directory, then we try
     * the location of the originating grammar.
     *
     * @param fileName input name we are looking for
     * @return Path to file that we think shuold be the import file
     *
     * @throws java.io.IOException
     */
    public String getLibraryFile(String fileName) throws IOException {

        // First, see if we can find the file in the library directory
        //
        File f = new File(getLibraryDirectory() + File.separator + fileName);

        if (f.exists()) {

            // Found in the library directory
            //
            return f.getAbsolutePath();
        }

        // Need to assume it is in the same location as the input file. Note that
        // this is only relevant for external build tools and when the input grammar
        // was specified relative to the source directory (working directory if using
        // the command line.
        //
        return parentGrammarDirectory + File.separator + fileName;
    }

    /** Return the directory containing the grammar file for this grammar.
     *  normally this is a relative path from current directory.  People will
     *  often do "java org.antlr.Tool grammars/*.g3"  So the file will be
     *  "grammars/foo.g3" etc...  This method returns "grammars".
     *
     *  If we have been given a specific input directory as a base, then
     *  we must find the directory relative to this directory, unless the
     *  file name is given to us in absolute terms.
     */
    public String getFileDirectory(String fileName) {

        File f;
        if (haveInputDir && !fileName.startsWith(File.separator)) {
            f = new File(inputDirectory, fileName);
        }
        else {
            f = new File(fileName);
        }
        // And ask Java what the base directory of this location is
        //
        return f.getParent();
    }

    /** Return a File descriptor for vocab file.  Look in library or
     *  in -o output path.  antlr -o foo T.g U.g where U needs T.tokens
     *  won't work unless we look in foo too. If we do not find the
     *  file in the lib directory then must assume that the .tokens file
     *  is going to be generated as part of this build and we have defined
     *  .tokens files so that they ALWAYS are generated in the base output
     *  directory, which means the current directory for the command line tool if there
     *  was no output directory specified.
     */
    public File getImportedVocabFile(String vocabName) {

        File f = new File(getLibraryDirectory(),
                          File.separator +
                          vocabName +
                          CodeGenerator.VOCAB_FILE_EXTENSION);
        if (f.exists()) {
            return f;
        }

        // We did not find the vocab file in the lib directory, so we need
        // to look for it in the output directory which is where .tokens
        // files are generated (in the base, not relative to the input
        // location.)
        //
        if (haveOutputDir) {
            f = new File(getOutputDirectory(), vocabName + CodeGenerator.VOCAB_FILE_EXTENSION);
        }
        else {
            f = new File(vocabName + CodeGenerator.VOCAB_FILE_EXTENSION);
        }
        return f;
    }

    /** If the tool needs to panic/exit, how do we do that?
     */
    public void panic() {
        throw new Error("ANTLR panic");
    }

    /** Return a time stamp string accurate to sec: yyyy-mm-dd hh:mm:ss
     */
    public static String getCurrentTimeStamp() {
        GregorianCalendar calendar = new java.util.GregorianCalendar();
        int y = calendar.get(Calendar.YEAR);
        int m = calendar.get(Calendar.MONTH) + 1; // zero-based for months
        int d = calendar.get(Calendar.DAY_OF_MONTH);
        int h = calendar.get(Calendar.HOUR_OF_DAY);
        int min = calendar.get(Calendar.MINUTE);
        int sec = calendar.get(Calendar.SECOND);
        String sy = String.valueOf(y);
        String sm = m < 10 ? "0" + m : String.valueOf(m);
        String sd = d < 10 ? "0" + d : String.valueOf(d);
        String sh = h < 10 ? "0" + h : String.valueOf(h);
        String smin = min < 10 ? "0" + min : String.valueOf(min);
        String ssec = sec < 10 ? "0" + sec : String.valueOf(sec);
        return new StringBuffer().append(sy).append("-").append(sm).append("-").append(sd).append(" ").append(sh).append(":").append(smin).append(":").append(ssec).toString();
    }

    /**
     * Provide the List of all grammar file names that the ANTLR tool will
     * process or has processed.
     *
     * @return the grammarFileNames
     */
    public List<String> getGrammarFileNames() {
        return grammarFileNames;
    }

    /**
     * Indicates whether ANTLR has gnerated or will generate a description of
     * all the NFAs in <a href="http://www.graphviz.org">Dot format</a>
     *
     * @return the generate_NFA_dot
     */
    public boolean isGenerate_NFA_dot() {
        return generate_NFA_dot;
    }

    /**
     * Indicates whether ANTLR has generated or will generate a description of
     * all the NFAs in <a href="http://www.graphviz.org">Dot format</a>
     *
     * @return the generate_DFA_dot
     */
    public boolean isGenerate_DFA_dot() {
        return generate_DFA_dot;
    }

    /**
     * Return the Path to the base output directory, where ANTLR
     * will generate all the output files for the current language target as
     * well as any ancillary files such as .tokens vocab files.
     *
     * @return the output Directory
     */
    public String getOutputDirectory() {
        return outputDirectory;
    }

    /**
     * Return the Path to the directory in which ANTLR will search for ancillary
     * files such as .tokens vocab files and imported grammar files.
     *
     * @return the lib Directory
     */
    public String getLibraryDirectory() {
        return libDirectory;
    }

    /**
     * Indicate if ANTLR has generated, or will generate a debug version of the
     * recognizer. Debug versions of a parser communicate with a debugger such
     * as that contained in ANTLRWorks and at start up will 'hang' waiting for
     * a connection on an IP port (49100 by default).
     *
     * @return the debug flag
     */
    public boolean isDebug() {
        return debug;
    }

    /**
     * Indicate whether ANTLR has generated, or will generate a version of the
     * recognizer that prints trace messages on entry and exit of each rule.
     *
     * @return the trace flag
     */
    public boolean isTrace() {
        return trace;
    }

    /**
     * Indicates whether ANTLR has generated or will generate a version of the
     * recognizer that gathers statistics about its execution, which it prints when
     * it terminates.
     *
     * @return the profile
     */
    public boolean isProfile() {
        return profile;
    }

    /**
     * Indicates whether ANTLR has generated or will generate a report of various
     * elements of the grammar analysis, once it it has finished analyzing a grammar
     * file.
     *
     * @return the report flag
     */
    public boolean isReport() {
        return report;
    }

    /**
     * Indicates whether ANTLR has printed, or will print, a version of the input grammar
     * file(s) that is stripped of any action code embedded within.
     *
     * @return the printGrammar flag
     */
    public boolean isPrintGrammar() {
        return printGrammar;
    }

    /**
     * Indicates whether ANTLR has supplied, or will supply, a list of all the things
     * that the input grammar depends upon and all the things that will be generated
     * when that grammar is successfully analyzed.
     *
     * @return the depend flag
     */
    public boolean isDepend() {
        return depend;
    }

    /**
     * Indicates whether ANTLR will force all files to the output directory, even
     * if the input files have relative paths from the input directory.
     *
     * @return the forceAllFilesToOutputDir flag
     */
    public boolean isForceAllFilesToOutputDir() {
        return forceAllFilesToOutputDir;
    }

    /**
     * Indicates whether ANTLR will be verbose when analyzing grammar files, such as
     * displaying the names of the files it is generating and similar information.
     *
     * @return the verbose flag
     */
    public boolean isVerbose() {
        return verbose;
    }

    /**
     * Provide the current setting of the conversion timeout on DFA creation.
     *
     * @return DFA creation timeout value in milliseconds
     */
    public int getConversionTimeout() {
        return DFA.MAX_TIME_PER_DFA_CREATION;
    }

    /**
     * Returns the current setting of the message format descriptor
     * @return Current message format
     */
    public String getMessageFormat() {
        return ErrorManager.getMessageFormat().toString();
    }

    /**
     * Returns the number of errors that the analysis/processing threw up.
     * @return Error count
     */
    public int getNumErrors() {
        return ErrorManager.getNumErrors();
    }

    /**
     * Indicate whether the tool will analyze the dependencies of the provided grammar
     * file list and ensure that grammars with dependencies are built
     * after any of the other gramamrs in the list that they are dependent on. Setting
     * this option also has the side effect that any grammars that are includes for other
     * grammars in the list are excluded from individual analysis, which allows the caller
     * to invoke the tool via org.antlr.tool -make *.g and not worry about the inclusion
     * of grammars that are just includes for other grammars or what order the grammars
     * appear on the command line.
     *
     * This option was coded to make life easier for tool integration (such as Maven) but
     * may also be useful at the command line.
     *
     * @return true if the tool is currently configured to analyze and sort grammar files.
     */
    public boolean getMake() {
        return make;
    }

    /**
     * Set the message format to one of ANTLR, gnu, vs2005
     *
     * @param format
     */
    public void setMessageFormat(String format) {
        ErrorManager.setFormat(format);
    }

    /** Provide the List of all grammar file names that the ANTLR tool should process.
     *
     * @param grammarFileNames The list of grammar files to process
     */
    public void setGrammarFileNames(List<String> grammarFileNames) {
        this.grammarFileNames = grammarFileNames;
    }

    public void addGrammarFile(String grammarFileName) {
        if (!grammarFileNames.contains(grammarFileName)) {
            grammarFileNames.add(grammarFileName);
        }
    }

    /**
     * Indicate whether ANTLR should generate a description of
     * all the NFAs in <a href="http://www.graphviz.org">Dot format</a>
     *
     * @param generate_NFA_dot True to generate dot descriptions
     */
    public void setGenerate_NFA_dot(boolean generate_NFA_dot) {
        this.generate_NFA_dot = generate_NFA_dot;
    }

    /**
     * Indicates whether ANTLR should generate a description of
     * all the NFAs in <a href="http://www.graphviz.org">Dot format</a>
     *
     * @param generate_DFA_dot True to generate dot descriptions
     */
    public void setGenerate_DFA_dot(boolean generate_DFA_dot) {
        this.generate_DFA_dot = generate_DFA_dot;
    }

    /**
     * Set the Path to the directory in which ANTLR will search for ancillary
     * files such as .tokens vocab files and imported grammar files.
     *
     * @param libDirectory the libDirectory to set
     */
    public void setLibDirectory(String libDirectory) {
        this.libDirectory = libDirectory;
    }

    /**
     * Indicate whether ANTLR should generate a debug version of the
     * recognizer. Debug versions of a parser communicate with a debugger such
     * as that contained in ANTLRWorks and at start up will 'hang' waiting for
     * a connection on an IP port (49100 by default).
     *
     * @param debug true to generate a debug mode parser
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * Indicate whether ANTLR should generate a version of the
     * recognizer that prints trace messages on entry and exit of each rule
     *
     * @param trace true to generate a tracing parser
     */
    public void setTrace(boolean trace) {
        this.trace = trace;
    }

    /**
     * Indicate whether ANTLR should generate a version of the
     * recognizer that gathers statistics about its execution, which it prints when
     * it terminates.
     *
     * @param profile true to generate a profiling parser
     */
    public void setProfile(boolean profile) {
        this.profile = profile;
    }

    /**
     * Indicate whether ANTLR should generate a report of various
     * elements of the grammar analysis, once it it has finished analyzing a grammar
     * file.
     *
     * @param report true to generate the analysis report
     */
    public void setReport(boolean report) {
        this.report = report;
    }

    /**
     * Indicate whether ANTLR should print a version of the input grammar
     * file(s) that is stripped of any action code embedded within.
     *
     * @param printGrammar true to generate a stripped file
     */
    public void setPrintGrammar(boolean printGrammar) {
        this.printGrammar = printGrammar;
    }

    /**
     * Indicate whether ANTLR should supply a list of all the things
     * that the input grammar depends upon and all the things that will be generated
     * when that gramamr is successfully analyzed.
     *
     * @param depend true to get depends set rather than process the grammar
     */
    public void setDepend(boolean depend) {
        this.depend = depend;
    }

    /**
     * Indicates whether ANTLR will force all files to the output directory, even
     * if the input files have relative paths from the input directory.
     *
     * @param forceAllFilesToOutputDir true to force files to output directory
     */
    public void setForceAllFilesToOutputDir(boolean forceAllFilesToOutputDir) {
        this.forceAllFilesToOutputDir = forceAllFilesToOutputDir;
    }

    /**
     * Indicate whether ANTLR should be verbose when analyzing grammar files, such as
     * displaying the names of the files it is generating and similar information.
     *
     * @param verbose true to be verbose
     */
    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }

    /**
     * Indicate whether the tool should analyze the dependencies of the provided grammar
     * file list and ensure that the grammars with dependencies are built
     * after any of the other gramamrs in the list that they are dependent on. Setting
     * this option also has the side effect that any grammars that are includes for other
     * grammars in the list are excluded from individual analysis, which allows the caller
     * to invoke the tool via org.antlr.tool -make *.g and not worry about the inclusion
     * of grammars that are just includes for other grammars or what order the grammars
     * appear on the command line.
     *
     * This option was coded to make life easier for tool integration (such as Maven) but
     * may also be useful at the command line.
     *
     * @param make
     */
    public void setMake(boolean make) {
        this.make = make;
    }

}
