1/*
2 * [The "BSD license"]
3 *  Copyright (c) 2010 Terence Parr
4 *  All rights reserved.
5 *
6 *  Redistribution and use in source and binary forms, with or without
7 *  modification, are permitted provided that the following conditions
8 *  are met:
9 *  1. Redistributions of source code must retain the above copyright
10 *      notice, this list of conditions and the following disclaimer.
11 *  2. Redistributions in binary form must reproduce the above copyright
12 *      notice, this list of conditions and the following disclaimer in the
13 *      documentation and/or other materials provided with the distribution.
14 *  3. The name of the author may not be used to endorse or promote products
15 *      derived from this software without specific prior written permission.
16 *
17 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28package org.antlr.tool;
29
30import org.antlr.Tool;
31import org.antlr.codegen.CodeGenerator;
32import org.antlr.misc.Utils;
33import org.stringtemplate.v4.ST;
34import org.stringtemplate.v4.STGroup;
35import org.stringtemplate.v4.STGroupFile;
36
37import java.io.*;
38import java.util.ArrayList;
39import java.util.List;
40
41/** Given a grammar file, show the dependencies on .tokens etc...
42 *  Using ST, emit a simple "make compatible" list of dependencies.
43 *  For example, combined grammar T.g (no token import) generates:
44 *
45 *		TParser.java : T.g
46 * 		T.tokens : T.g
47 * 		T__g : T.g
48 *
49 *  For tree grammar TP with import of T.tokens:
50 *
51 * 		TP.g : T.tokens
52 * 		TP.java : TP.g
53 *
54 *  If "-lib libdir" is used on command-line with -depend, then include the
55 *  path like
56 *
57 * 		TP.g : libdir/T.tokens
58 *
59 *  Pay attention to -o as well:
60 *
61 * 		outputdir/TParser.java : T.g
62 *
63 *  So this output shows what the grammar depends on *and* what it generates.
64 *
65 *  Operate on one grammar file at a time.  If given a list of .g on the
66 *  command-line with -depend, just emit the dependencies.  The grammars
67 *  may depend on each other, but the order doesn't matter.  Build tools,
68 *  reading in this output, will know how to organize it.
69 *
70 *  This is a wee bit slow probably because the code generator has to load
71 *  all of its template files in order to figure out the file extension
72 *  for the generated recognizer.
73 *
74 *  This code was obvious until I removed redundant "./" on front of files
75 *  and had to escape spaces in filenames :(
76 */
77public class BuildDependencyGenerator {
78    protected String grammarFileName;
79    protected String tokenVocab;
80    protected Tool tool;
81    protected Grammar grammar;
82    protected CodeGenerator generator;
83    protected STGroup templates;
84
85    public BuildDependencyGenerator(Tool tool, String grammarFileName)
86            throws IOException {
87        this.tool = tool;
88        this.grammarFileName = grammarFileName;
89        grammar = tool.getRootGrammar(grammarFileName);
90        String language = (String) grammar.getOption("language");
91        generator = new CodeGenerator(tool, grammar, language);
92        generator.loadTemplates(language);
93    }
94
95    /** From T.g return a list of File objects that
96     *  name files ANTLR will emit from T.g.
97     */
98    public List<File> getGeneratedFileList() {
99        List<File> files = new ArrayList<File>();
100        File outputDir = tool.getOutputDirectory(grammarFileName);
101        if (outputDir.getName().equals(".")) {
102            outputDir = null;
103        } else if (outputDir.getName().indexOf(' ') >= 0) { // has spaces?
104            String escSpaces = Utils.replace(outputDir.toString(),
105                    " ",
106                    "\\ ");
107            outputDir = new File(escSpaces);
108        }
109        // add generated recognizer; e.g., TParser.java
110        String recognizer =
111                generator.getRecognizerFileName(grammar.name, grammar.type);
112        files.add(new File(outputDir, recognizer));
113        // add output vocab file; e.g., T.tokens. This is always generated to
114        // the base output directory, which will be just . if there is no -o option
115        //
116        files.add(new File(tool.getOutputDirectory(), generator.getVocabFileName()));
117        // are we generating a .h file?
118        ST headerExtST = null;
119        ST extST = generator.getTemplates().getInstanceOf("codeFileExtension");
120        if (generator.getTemplates().isDefined("headerFile")) {
121            headerExtST = generator.getTemplates().getInstanceOf("headerFileExtension");
122            String suffix = Grammar.grammarTypeToFileNameSuffix[grammar.type];
123            String fileName = grammar.name + suffix + headerExtST.render();
124            files.add(new File(outputDir, fileName));
125        }
126        if (grammar.type == Grammar.COMBINED) {
127            // add autogenerated lexer; e.g., TLexer.java TLexer.h TLexer.tokens
128            // don't add T__.g (just a temp file)
129
130            String suffix = Grammar.grammarTypeToFileNameSuffix[Grammar.LEXER];
131            String lexer = grammar.name + suffix + extST.render();
132            files.add(new File(outputDir, lexer));
133
134            // TLexer.h
135            if (headerExtST != null) {
136                String header = grammar.name + suffix + headerExtST.render();
137                files.add(new File(outputDir, header));
138            }
139        // for combined, don't generate TLexer.tokens
140        }
141
142        // handle generated files for imported grammars
143        List<Grammar> imports =
144                grammar.composite.getDelegates(grammar.composite.getRootGrammar());
145        for (Grammar g : imports) {
146            outputDir = tool.getOutputDirectory(g.getFileName());
147            String fname = groomQualifiedFileName(outputDir.toString(), g.getRecognizerName() + extST.render());
148            files.add(new File(fname));
149        }
150
151        if (files.size() == 0) {
152            return null;
153        }
154        return files;
155    }
156
157    /**
158     * Return a list of File objects that name files ANTLR will read
159     * to process T.g; This can be .tokens files if the grammar uses the tokenVocab option
160     * as well as any imported grammar files.
161     */
162    public List<File> getDependenciesFileList() {
163        // Find all the things other than imported grammars
164        List<File> files = getNonImportDependenciesFileList();
165
166        // Handle imported grammars
167        List<Grammar> imports =
168                grammar.composite.getDelegates(grammar.composite.getRootGrammar());
169        for (Grammar g : imports) {
170            String libdir = tool.getLibraryDirectory();
171            String fileName = groomQualifiedFileName(libdir, g.fileName);
172            files.add(new File(fileName));
173        }
174
175        if (files.size() == 0) {
176            return null;
177        }
178        return files;
179    }
180
181    /**
182     * Return a list of File objects that name files ANTLR will read
183     * to process T.g; This can only be .tokens files and only
184     * if they use the tokenVocab option.
185     *
186     * @return List of dependencies other than imported grammars
187     */
188    public List<File> getNonImportDependenciesFileList() {
189        List<File> files = new ArrayList<File>();
190
191        // handle token vocabulary loads
192        tokenVocab = (String) grammar.getOption("tokenVocab");
193        if (tokenVocab != null) {
194
195            File vocabFile = tool.getImportedVocabFile(tokenVocab);
196            files.add(vocabFile);
197        }
198
199        return files;
200    }
201
202    public ST getDependencies() {
203        loadDependencyTemplates();
204        ST dependenciesST = templates.getInstanceOf("dependencies");
205        dependenciesST.add("in", getDependenciesFileList());
206        dependenciesST.add("out", getGeneratedFileList());
207        dependenciesST.add("grammarFileName", grammar.fileName);
208        return dependenciesST;
209    }
210
211    public void loadDependencyTemplates() {
212        if (templates != null) return;
213        String fileName = "org/antlr/tool/templates/depend.stg";
214        templates = new STGroupFile(fileName);
215    }
216
217    public String getTokenVocab() {
218        return tokenVocab;
219    }
220
221    public CodeGenerator getGenerator() {
222        return generator;
223    }
224
225    public String groomQualifiedFileName(String outputDir, String fileName) {
226        if (outputDir.equals(".")) {
227            return fileName;
228        } else if (outputDir.indexOf(' ') >= 0) { // has spaces?
229            String escSpaces = Utils.replace(outputDir.toString(),
230                    " ",
231                    "\\ ");
232            return escSpaces + File.separator + fileName;
233        } else {
234            return outputDir + File.separator + fileName;
235        }
236    }
237}
238