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: * Provides an anchor for a single source unit (usually a script file)
080: * as it passes through the compiler system.
081: *
082: * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
083: * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
084: * @version $Id: SourceUnit.java 4098 2006-10-10 16:09:48Z blackdrag $
085: */
086:
087: public class SourceUnit extends ProcessingUnit {
088:
089: /**
090: * The pluggable parser used to generate the AST - we allow pluggability currently as we need to have Classic and JSR support
091: */
092: private ParserPlugin parserPlugin;
093:
094: /**
095: * Where we can get Readers for our source unit
096: */
097: protected ReaderSource source;
098: /**
099: * A descriptive name of the source unit. This name shouldn't
100: * be used for controling the SourceUnit, it is only for error
101: * messages
102: */
103: protected String name;
104: /**
105: * A Concrete Syntax Tree of the source
106: */
107: protected Reduction cst;
108:
109: /**
110: * A facade over the CST
111: */
112: protected SourceSummary sourceSummary;
113: /**
114: * The root of the Abstract Syntax Tree for the source
115: */
116: protected ModuleNode ast;
117:
118: /**
119: * Initializes the SourceUnit from existing machinery.
120: */
121: public SourceUnit(String name, ReaderSource source,
122: CompilerConfiguration flags, GroovyClassLoader loader,
123: ErrorCollector er) {
124: super (flags, loader, er);
125:
126: this .name = name;
127: this .source = source;
128: }
129:
130: /**
131: * Initializes the SourceUnit from the specified file.
132: */
133: public SourceUnit(File source, CompilerConfiguration configuration,
134: GroovyClassLoader loader, ErrorCollector er) {
135: this (source.getPath(), new FileReaderSource(source,
136: configuration), configuration, loader, er);
137: }
138:
139: /**
140: * Initializes the SourceUnit from the specified URL.
141: */
142: public SourceUnit(URL source, CompilerConfiguration configuration,
143: GroovyClassLoader loader, ErrorCollector er) {
144: this (source.getPath(), new URLReaderSource(source,
145: configuration), configuration, loader, er);
146: }
147:
148: /**
149: * Initializes the SourceUnit for a string of source.
150: */
151: public SourceUnit(String name, String source,
152: CompilerConfiguration configuration,
153: GroovyClassLoader loader, ErrorCollector er) {
154: this (name, new StringReaderSource(source, configuration),
155: configuration, loader, er);
156: }
157:
158: /**
159: * Returns the name for the SourceUnit. This name shouldn't
160: * be used for controling the SourceUnit, it is only for error
161: * messages
162: */
163: public String getName() {
164: return name;
165: }
166:
167: /**
168: * Returns the Concrete Syntax Tree produced during parse()ing.
169: */
170: public Reduction getCST() {
171: return this .cst;
172: }
173:
174: /**
175: * Returns the Source Summary
176: */
177: public SourceSummary getSourceSummary() {
178: return this .sourceSummary;
179: }
180:
181: /**
182: * Returns the Abstract Syntax Tree produced during parse()ing
183: * and expanded during later phases.
184: */
185: public ModuleNode getAST() {
186: return this .ast;
187: }
188:
189: /**
190: * Convenience routine, primarily for use by the InteractiveShell,
191: * that returns true if parse() failed with an unexpected EOF.
192: */
193: public boolean failedWithUnexpectedEOF() {
194: // Implementation note - there are several ways for the Groovy compiler
195: // to report an unexpected EOF. Perhaps this implementation misses some.
196: // If you find another way, please add it.
197: if (getErrorCollector().hasErrors()) {
198: Message last = (Message) getErrorCollector().getLastError();
199: Throwable cause = null;
200: if (last instanceof SyntaxErrorMessage) {
201: cause = ((SyntaxErrorMessage) last).getCause()
202: .getCause();
203: }
204: if (cause != null) {
205: if (cause instanceof NoViableAltException) {
206: return isEofToken(((NoViableAltException) cause).token);
207: } else if (cause instanceof NoViableAltForCharException) {
208: char badChar = ((NoViableAltForCharException) cause).foundChar;
209: return badChar == CharScanner.EOF_CHAR;
210: } else if (cause instanceof MismatchedTokenException) {
211: return isEofToken(((MismatchedTokenException) cause).token);
212: }
213: }
214: }
215: return false;
216: }
217:
218: protected boolean isEofToken(antlr.Token token) {
219: return token.getType() == antlr.Token.EOF_TYPE;
220: }
221:
222: //---------------------------------------------------------------------------
223: // FACTORIES
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,
234: new ErrorCollector(configuration));
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,
242: int tolerance) {
243: CompilerConfiguration configuration = new CompilerConfiguration();
244: configuration.setTolerance(tolerance);
245:
246: return new SourceUnit(name, source, configuration, null,
247: new ErrorCollector(configuration));
248: }
249:
250: //---------------------------------------------------------------------------
251: // PROCESSING
252:
253: /**
254: * Parses the source to a CST. You can retrieve it with getCST().
255: */
256: public void parse() throws CompilationFailedException {
257: if (this .phase > Phases.PARSING) {
258: throw new GroovyBugError("parsing is already complete");
259: }
260:
261: if (this .phase == Phases.INITIALIZATION) {
262: nextPhase();
263: }
264:
265: //
266: // Create a reader on the source and run the parser.
267:
268: Reader reader = null;
269: try {
270: reader = source.getReader();
271:
272: // lets recreate the parser each time as it tends to keep around state
273: parserPlugin = getConfiguration().getPluginFactory()
274: .createParserPlugin();
275:
276: cst = parserPlugin.parseCST(this , reader);
277: sourceSummary = parserPlugin.getSummary();
278:
279: reader.close();
280:
281: } catch (IOException e) {
282: getErrorCollector().addFatalError(
283: new SimpleMessage(e.getMessage(), this ));
284: } finally {
285: if (reader != null) {
286: try {
287: reader.close();
288: } catch (IOException e) {
289: }
290: }
291: }
292: }
293:
294: /**
295: * Generates an AST from the CST. You can retrieve it with getAST().
296: */
297: public void convert() throws CompilationFailedException {
298: if (this .phase == Phases.PARSING && this .phaseComplete) {
299: gotoPhase(Phases.CONVERSION);
300: }
301:
302: if (this .phase != Phases.CONVERSION) {
303: throw new GroovyBugError(
304: "SourceUnit not ready for convert()");
305: }
306:
307: //
308: // Build the AST
309:
310: try {
311: this .ast = parserPlugin.buildAST(this , this .classLoader,
312: this .cst);
313:
314: this .ast.setDescription(this .name);
315: } catch (SyntaxException e) {
316: getErrorCollector().addError(
317: new SyntaxErrorMessage(e, this ));
318: }
319:
320: String property = (String) AccessController
321: .doPrivileged(new PrivilegedAction() {
322: public Object run() {
323: return System.getProperty("groovy.ast");
324: }
325: });
326:
327: if ("xml".equals(property)) {
328: saveAsXML(name, ast);
329: }
330: }
331:
332: private void saveAsXML(String name, ModuleNode ast) {
333: XStream xstream = new XStream();
334: try {
335: xstream.toXML(ast, new FileWriter(name + ".xml"));
336: System.out.println("Written AST to " + name + ".xml");
337: } catch (Exception e) {
338: System.out.println("Couldn't write to " + name + ".xml");
339: e.printStackTrace();
340: }
341: }
342:
343: //--------------------------------------------------------------------------- // SOURCE SAMPLING
344:
345: /**
346: * Returns a sampling of the source at the specified line and column,
347: * of null if it is unavailable.
348: */
349: public String getSample(int line, int column, Janitor janitor) {
350: String sample = null;
351: String text = source.getLine(line, janitor);
352:
353: if (text != null) {
354: if (column > 0) {
355: String marker = Utilities.repeatString(" ", column - 1)
356: + "^";
357:
358: if (column > 40) {
359: int start = column - 30 - 1;
360: int end = (column + 10 > text.length() ? text
361: .length() : column + 10 - 1);
362: sample = " " + text.substring(start, end)
363: + Utilities.eol() + " "
364: + marker.substring(start, marker.length());
365: } else {
366: sample = " " + text + Utilities.eol() + " "
367: + marker;
368: }
369: } else {
370: sample = text;
371: }
372: }
373:
374: return sample;
375: }
376:
377: public void addException(Exception e)
378: throws CompilationFailedException {
379: getErrorCollector().addException(e, this );
380: }
381:
382: public void addError(SyntaxException se)
383: throws CompilationFailedException {
384: getErrorCollector().addError(se, this);
385: }
386: }
|