001 /*
002 $Id: SourceUnit.java 4098 2006-10-10 16:09:48Z blackdrag $
003
004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005
006 Redistribution and use of this software and associated documentation
007 ("Software"), with or without modification, are permitted provided
008 that the following conditions are met:
009
010 1. Redistributions of source code must retain copyright
011 statements and notices. Redistributions must also contain a
012 copy of this document.
013
014 2. Redistributions in binary form must reproduce the
015 above copyright notice, this list of conditions and the
016 following disclaimer in the documentation and/or other
017 materials provided with the distribution.
018
019 3. The name "groovy" must not be used to endorse or promote
020 products derived from this Software without prior written
021 permission of The Codehaus. For written permission,
022 please contact info@codehaus.org.
023
024 4. Products derived from this Software may not be called "groovy"
025 nor may "groovy" appear in their names without prior written
026 permission of The Codehaus. "groovy" is a registered
027 trademark of The Codehaus.
028
029 5. Due credit should be given to The Codehaus -
030 http://groovy.codehaus.org/
031
032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043 OF THE POSSIBILITY OF SUCH DAMAGE.
044
045 */
046
047 package org.codehaus.groovy.control;
048
049 import groovy.lang.GroovyClassLoader;
050
051 import java.io.File;
052 import java.io.FileWriter;
053 import java.io.IOException;
054 import java.io.Reader;
055 import java.net.URL;
056 import java.security.AccessController;
057 import java.security.PrivilegedAction;
058
059 import org.codehaus.groovy.GroovyBugError;
060 import org.codehaus.groovy.ast.ModuleNode;
061 import org.codehaus.groovy.control.io.FileReaderSource;
062 import org.codehaus.groovy.control.io.ReaderSource;
063 import org.codehaus.groovy.control.io.StringReaderSource;
064 import org.codehaus.groovy.control.io.URLReaderSource;
065 import org.codehaus.groovy.control.messages.Message;
066 import org.codehaus.groovy.control.messages.SimpleMessage;
067 import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
068 import org.codehaus.groovy.syntax.*;
069 import org.codehaus.groovy.tools.Utilities;
070
071 import antlr.CharScanner;
072 import antlr.MismatchedTokenException;
073 import antlr.NoViableAltException;
074 import antlr.NoViableAltForCharException;
075
076 import com.thoughtworks.xstream.XStream;
077
078
079 /**
080 * Provides an anchor for a single source unit (usually a script file)
081 * as it passes through the compiler system.
082 *
083 * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
084 * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
085 * @version $Id: SourceUnit.java 4098 2006-10-10 16:09:48Z blackdrag $
086 */
087
088 public class SourceUnit extends ProcessingUnit {
089
090 /**
091 * The pluggable parser used to generate the AST - we allow pluggability currently as we need to have Classic and JSR support
092 */
093 private ParserPlugin parserPlugin;
094
095 /**
096 * Where we can get Readers for our source unit
097 */
098 protected ReaderSource source;
099 /**
100 * A descriptive name of the source unit. This name shouldn't
101 * be used for controling the SourceUnit, it is only for error
102 * messages
103 */
104 protected String name;
105 /**
106 * A Concrete Syntax Tree of the source
107 */
108 protected Reduction cst;
109
110 /**
111 * A facade over the CST
112 */
113 protected SourceSummary sourceSummary;
114 /**
115 * The root of the Abstract Syntax Tree for the source
116 */
117 protected ModuleNode ast;
118
119
120 /**
121 * Initializes the SourceUnit from existing machinery.
122 */
123 public SourceUnit(String name, ReaderSource source, CompilerConfiguration flags, GroovyClassLoader loader, ErrorCollector er) {
124 super(flags, loader, er);
125
126 this.name = name;
127 this.source = source;
128 }
129
130
131 /**
132 * Initializes the SourceUnit from the specified file.
133 */
134 public SourceUnit(File source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
135 this(source.getPath(), new FileReaderSource(source, configuration), configuration, loader, er);
136 }
137
138
139 /**
140 * Initializes the SourceUnit from the specified URL.
141 */
142 public SourceUnit(URL source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
143 this(source.getPath(), new URLReaderSource(source, configuration), configuration, loader, er);
144 }
145
146
147 /**
148 * Initializes the SourceUnit for a string of source.
149 */
150 public SourceUnit(String name, String source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
151 this(name, new StringReaderSource(source, configuration), configuration, loader, er);
152 }
153
154
155 /**
156 * Returns the name for the SourceUnit. This name shouldn't
157 * be used for controling the SourceUnit, it is only for error
158 * messages
159 */
160 public String getName() {
161 return name;
162 }
163
164
165 /**
166 * Returns the Concrete Syntax Tree produced during parse()ing.
167 */
168 public Reduction getCST() {
169 return this.cst;
170 }
171
172 /**
173 * Returns the Source Summary
174 */
175 public SourceSummary getSourceSummary() {
176 return this.sourceSummary;
177 }
178 /**
179 * Returns the Abstract Syntax Tree produced during parse()ing
180 * and expanded during later phases.
181 */
182 public ModuleNode getAST() {
183 return this.ast;
184 }
185
186
187 /**
188 * Convenience routine, primarily for use by the InteractiveShell,
189 * that returns true if parse() failed with an unexpected EOF.
190 */
191 public boolean failedWithUnexpectedEOF() {
192 // Implementation note - there are several ways for the Groovy compiler
193 // to report an unexpected EOF. Perhaps this implementation misses some.
194 // If you find another way, please add it.
195 if (getErrorCollector().hasErrors()) {
196 Message last = (Message) getErrorCollector().getLastError();
197 Throwable cause = null;
198 if (last instanceof SyntaxErrorMessage) {
199 cause = ((SyntaxErrorMessage) last).getCause().getCause();
200 }
201 if (cause != null) {
202 if (cause instanceof NoViableAltException) {
203 return isEofToken(((NoViableAltException) cause).token);
204 } else if (cause instanceof NoViableAltForCharException) {
205 char badChar = ((NoViableAltForCharException) cause).foundChar;
206 return badChar == CharScanner.EOF_CHAR;
207 } else if (cause instanceof MismatchedTokenException) {
208 return isEofToken(((MismatchedTokenException) cause).token);
209 }
210 }
211 }
212 return false;
213 }
214
215 protected boolean isEofToken(antlr.Token token) {
216 return token.getType() == antlr.Token.EOF_TYPE;
217 }
218
219
220
221 //---------------------------------------------------------------------------
222 // FACTORIES
223
224
225 /**
226 * A convenience routine to create a standalone SourceUnit on a String
227 * with defaults for almost everything that is configurable.
228 */
229 public static SourceUnit create(String name, String source) {
230 CompilerConfiguration configuration = new CompilerConfiguration();
231 configuration.setTolerance(1);
232
233 return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
234 }
235
236
237 /**
238 * A convenience routine to create a standalone SourceUnit on a String
239 * with defaults for almost everything that is configurable.
240 */
241 public static SourceUnit create(String name, String source, int tolerance) {
242 CompilerConfiguration configuration = new CompilerConfiguration();
243 configuration.setTolerance(tolerance);
244
245 return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
246 }
247
248
249
250
251
252 //---------------------------------------------------------------------------
253 // PROCESSING
254
255
256 /**
257 * Parses the source to a CST. You can retrieve it with getCST().
258 */
259 public void parse() throws CompilationFailedException {
260 if (this.phase > Phases.PARSING) {
261 throw new GroovyBugError("parsing is already complete");
262 }
263
264 if (this.phase == Phases.INITIALIZATION) {
265 nextPhase();
266 }
267
268
269 //
270 // Create a reader on the source and run the parser.
271
272 Reader reader = null;
273 try {
274 reader = source.getReader();
275
276 // lets recreate the parser each time as it tends to keep around state
277 parserPlugin = getConfiguration().getPluginFactory().createParserPlugin();
278
279 cst = parserPlugin.parseCST(this, reader);
280 sourceSummary = parserPlugin.getSummary();
281
282 reader.close();
283
284 }
285 catch (IOException e) {
286 getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(),this));
287 }
288 finally {
289 if (reader != null) {
290 try {
291 reader.close();
292 }
293 catch (IOException e) {
294 }
295 }
296 }
297 }
298
299
300 /**
301 * Generates an AST from the CST. You can retrieve it with getAST().
302 */
303 public void convert() throws CompilationFailedException {
304 if (this.phase == Phases.PARSING && this.phaseComplete) {
305 gotoPhase(Phases.CONVERSION);
306 }
307
308 if (this.phase != Phases.CONVERSION) {
309 throw new GroovyBugError("SourceUnit not ready for convert()");
310 }
311
312
313 //
314 // Build the AST
315
316 try {
317 this.ast = parserPlugin.buildAST(this, this.classLoader, this.cst);
318
319 this.ast.setDescription(this.name);
320 }
321 catch (SyntaxException e) {
322 getErrorCollector().addError(new SyntaxErrorMessage(e,this));
323 }
324
325 String property = (String) AccessController.doPrivileged(new PrivilegedAction() {
326 public Object run() {
327 return System.getProperty("groovy.ast");
328 }
329 });
330
331 if ("xml".equals(property)) {
332 saveAsXML(name,ast);
333 }
334 }
335
336 private void saveAsXML(String name, ModuleNode ast) {
337 XStream xstream = new XStream();
338 try {
339 xstream.toXML(ast,new FileWriter(name + ".xml"));
340 System.out.println("Written AST to " + name + ".xml");
341 } catch (Exception e) {
342 System.out.println("Couldn't write to " + name + ".xml");
343 e.printStackTrace();
344 }
345 }
346 //--------------------------------------------------------------------------- // SOURCE SAMPLING
347
348 /**
349 * Returns a sampling of the source at the specified line and column,
350 * of null if it is unavailable.
351 */
352 public String getSample(int line, int column, Janitor janitor) {
353 String sample = null;
354 String text = source.getLine(line, janitor);
355
356 if (text != null) {
357 if (column > 0) {
358 String marker = Utilities.repeatString(" ", column - 1) + "^";
359
360 if (column > 40) {
361 int start = column - 30 - 1;
362 int end = (column + 10 > text.length() ? text.length() : column + 10 - 1);
363 sample = " " + text.substring(start, end) + Utilities.eol() + " " + marker.substring(start, marker.length());
364 }
365 else {
366 sample = " " + text + Utilities.eol() + " " + marker;
367 }
368 }
369 else {
370 sample = text;
371 }
372 }
373
374 return sample;
375 }
376
377 public void addException(Exception e) throws CompilationFailedException {
378 getErrorCollector().addException(e,this);
379 }
380
381 public void addError(SyntaxException se) throws CompilationFailedException {
382 getErrorCollector().addError(se,this);
383 }
384 }
385
386
387
388