View Javadoc

1   /*
2    $Id: SourceUnit.java,v 1.12 2005/06/10 09:55:30 cstein Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  
47  package org.codehaus.groovy.control;
48  
49  import java.io.File;
50  import java.io.FileWriter;
51  import java.io.IOException;
52  import java.io.Reader;
53  import java.net.URL;
54  import java.util.List;
55  
56  import org.codehaus.groovy.GroovyBugError;
57  import org.codehaus.groovy.ast.ClassNode;
58  import org.codehaus.groovy.ast.FieldNode;
59  import org.codehaus.groovy.ast.MethodNode;
60  import org.codehaus.groovy.ast.ModuleNode;
61  import org.codehaus.groovy.ast.stmt.BlockStatement;
62  import org.codehaus.groovy.ast.stmt.Statement;
63  import org.codehaus.groovy.control.io.FileReaderSource;
64  import org.codehaus.groovy.control.io.ReaderSource;
65  import org.codehaus.groovy.control.io.StringReaderSource;
66  import org.codehaus.groovy.control.io.URLReaderSource;
67  import org.codehaus.groovy.control.messages.ExceptionMessage;
68  import org.codehaus.groovy.control.messages.Message;
69  import org.codehaus.groovy.control.messages.SimpleMessage;
70  import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
71  import org.codehaus.groovy.syntax.Reduction;
72  import org.codehaus.groovy.syntax.SyntaxException;
73  import org.codehaus.groovy.syntax.Token;
74  import org.codehaus.groovy.syntax.Types;
75  import org.codehaus.groovy.syntax.UnexpectedTokenException;
76  import org.codehaus.groovy.tools.Utilities;
77  
78  import antlr.MismatchedTokenException;
79  import antlr.NoViableAltException;
80  
81  import com.thoughtworks.xstream.XStream;
82  
83  
84  /***
85   * Provides an anchor for a single source unit (usually a script file)
86   * as it passes through the compiler system.
87   *
88   * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
89   * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
90   * @version $Id: SourceUnit.java,v 1.12 2005/06/10 09:55:30 cstein Exp $
91   */
92  
93  public class SourceUnit extends ProcessingUnit {
94  
95      /***
96       * The pluggable parser used to generate the AST - we allow pluggability currently as we need to have Classic and JSR support
97       */
98      private ParserPlugin parserPlugin;
99  
100     /***
101      * Where we can get Readers for our source unit
102      */
103     protected ReaderSource source;
104     /***
105      * A descriptive name of the source unit
106      */
107     protected String name;
108     /***
109      * A Concrete Syntax Tree of the source
110      */
111     protected Reduction cst;
112     /***
113      * The root of the Abstract Syntax Tree for the source
114      */
115     protected ModuleNode ast;
116 
117 
118     /***
119      * Initializes the SourceUnit from existing machinery.
120      */
121     public SourceUnit(String name, ReaderSource source, CompilerConfiguration flags, ClassLoader loader, ErrorCollector er) {
122         super(flags, loader, er);
123 
124         this.name = name;
125         this.source = source;
126     }
127 
128 
129     /***
130      * Initializes the SourceUnit from the specified file.
131      */
132     public SourceUnit(File source, CompilerConfiguration configuration, ClassLoader loader, ErrorCollector er) {
133         this(source.getPath(), new FileReaderSource(source, configuration), configuration, loader, er);
134     }
135 
136 
137     /***
138      * Initializes the SourceUnit from the specified URL.
139      */
140     public SourceUnit(URL source, CompilerConfiguration configuration, ClassLoader loader, ErrorCollector er) {
141         this(source.getPath(), new URLReaderSource(source, configuration), configuration, loader, er);
142     }
143 
144 
145     /***
146      * Initializes the SourceUnit for a string of source.
147      */
148     public SourceUnit(String name, String source, CompilerConfiguration configuration, ClassLoader loader, ErrorCollector er) {
149         this(name, new StringReaderSource(source, configuration), configuration, loader, er);
150     }
151 
152 
153     /***
154      * Returns the name for the SourceUnit.
155      */
156     public String getName() {
157         return name;
158     }
159 
160 
161     /***
162      * Returns the Concrete Syntax Tree produced during parse()ing.
163      */
164     public Reduction getCST() {
165         return this.cst;
166     }
167 
168 
169     /***
170      * Returns the Abstract Syntax Tree produced during parse()ing
171      * and expanded during later phases.
172      */
173     public ModuleNode getAST() {
174         return this.ast;
175     }
176 
177 
178     /***
179      * Convenience routine, primarily for use by the InteractiveShell,
180      * that returns true if parse() failed with an unexpected EOF.
181      */
182     public boolean failedWithUnexpectedEOF() {
183         boolean result = false;
184         if (getErrorCollector().hasErrors()) {
185             // Classic support
186             Message last = (Message) getErrorCollector().getLastError();
187             if (last instanceof SyntaxErrorMessage) {
188                 SyntaxException cause = ((SyntaxErrorMessage) last).getCause();
189                 if (cause instanceof UnexpectedTokenException) {
190                     Token unexpected = ((UnexpectedTokenException) cause).getUnexpectedToken();
191                     if (unexpected.isA(Types.EOF)) {
192                         result = true;
193                     }
194                 }
195             }
196             // JSR support
197             if (last instanceof ExceptionMessage) {
198                 ExceptionMessage exceptionMessage = (ExceptionMessage) last;
199                 Exception cause = exceptionMessage.getCause();
200                 if (cause instanceof NoViableAltException) {
201                     NoViableAltException antlrException = (NoViableAltException) cause;
202                     result = isEofToken(antlrException.token);
203                 }
204                 if (cause instanceof MismatchedTokenException) {
205                     MismatchedTokenException antlrException = (MismatchedTokenException) cause;
206                     result = isEofToken(antlrException.token);
207                 }
208             }
209         }
210         return result;    
211     }
212 
213     protected boolean isEofToken(antlr.Token token) {
214         return token.getType() == antlr.Token.EOF_TYPE;
215     }
216 
217 
218 
219     //---------------------------------------------------------------------------
220     // FACTORIES
221 
222 
223     /***
224      * A convenience routine to create a standalone SourceUnit on a String
225      * with defaults for almost everything that is configurable.
226      */
227     public static SourceUnit create(String name, String source) {
228         CompilerConfiguration configuration = new CompilerConfiguration();
229         configuration.setTolerance(1);
230 
231         return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
232     }
233 
234 
235     /***
236      * A convenience routine to create a standalone SourceUnit on a String
237      * with defaults for almost everything that is configurable.
238      */
239     public static SourceUnit create(String name, String source, int tolerance) {
240         CompilerConfiguration configuration = new CompilerConfiguration();
241         configuration.setTolerance(tolerance);
242 
243         return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration));
244     }
245 
246 
247 
248 
249 
250     //---------------------------------------------------------------------------
251     // PROCESSING
252 
253 
254     /***
255      * Parses the source to a CST.  You can retrieve it with getCST().
256      */
257     public void parse() throws CompilationFailedException {
258         if (this.phase > Phases.PARSING) {
259             throw new GroovyBugError("parsing is already complete");
260         }
261 
262         if (this.phase == Phases.INITIALIZATION) {
263             nextPhase();
264         }
265 
266 
267         //
268         // Create a reader on the source and run the parser.
269 
270         Reader reader = null;
271         try {
272             reader = source.getReader();
273 
274             // lets recreate the parser each time as it tends to keep around state
275             parserPlugin = getConfiguration().getPluginFactory().createParserPlugin();
276 
277             cst = parserPlugin.parseCST(this, reader);
278 
279             reader.close();
280             completePhase();
281             
282         }
283         catch (IOException e) {
284             getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(),this));
285         }
286         finally {
287             if (reader != null) {
288                 try {
289                     reader.close();
290                 }
291                 catch (IOException e) {
292                 }
293             }
294         }
295     }
296 
297 
298     /***
299      * Generates an AST from the CST.  You can retrieve it with getAST().
300      */
301     public void convert() throws CompilationFailedException {
302         if (this.phase == Phases.PARSING && this.phaseComplete) {
303             gotoPhase(Phases.CONVERSION);
304         }
305 
306         if (this.phase != Phases.CONVERSION) {
307             throw new GroovyBugError("SourceUnit not ready for convert()");
308         }
309 
310         
311         //
312         // Build the AST
313         
314         try {
315             this.ast = parserPlugin.buildAST(this, this.classLoader, this.cst);
316 
317             this.ast.setDescription(this.name);
318         }
319         catch (SyntaxException e) {
320             getErrorCollector().addError(new SyntaxErrorMessage(e,this));
321         }
322 
323         if ("xml".equals(System.getProperty("groovy.ast"))) {
324             saveAsXML(name,ast);
325         }
326 
327         completePhase();
328     }
329 
330     private void saveAsXML(String name, ModuleNode ast) {
331         XStream xstream = new XStream();
332         try {
333             xstream.toXML(ast,new FileWriter(name + ".xml"));
334             System.out.println("Written AST to " + name + ".xml");
335         } catch (Exception e) {
336             System.out.println("Couldn't write to " + name + ".xml");
337             e.printStackTrace();
338         }
339     }
340     //---------------------------------------------------------------------------    // SOURCE SAMPLING
341 
342     /***
343      * Returns a sampling of the source at the specified line and column,
344      * of null if it is unavailable.
345      */
346     public String getSample(int line, int column, Janitor janitor) {
347         String sample = null;
348         String text = source.getLine(line, janitor);
349 
350         if (text != null) {
351             if (column > 0) {
352                 String marker = Utilities.repeatString(" ", column - 1) + "^";
353 
354                 if (column > 40) {
355                     int start = column - 30 - 1;
356                     int end = (column + 10 > text.length() ? text.length() : column + 10 - 1);
357                     sample = "   " + text.substring(start, end) + Utilities.eol() + "   " + marker.substring(start, marker.length());
358                 }
359                 else {
360                     sample = "   " + text + Utilities.eol() + "   " + marker;
361                 }
362             }
363             else {
364                 sample = text;
365             }
366         }
367 
368         return sample;
369     }
370 
371     /***
372      * to quickly create a ModuleNode from a piece of Groovy code
373      *
374      * @param code
375      * @return
376      * @throws CompilationFailedException
377      */
378     public static ModuleNode createModule(String code) throws CompilationFailedException {
379         SourceUnit su = create("NodeGen", code);
380         su.parse();
381         su.convert();
382         return su.getAST();
383     }
384 
385     public static ClassNode createClassNode(String code) throws CompilationFailedException {
386         ModuleNode module = createModule(code);
387         List classes = module.getClasses();
388         if (classes.size() > 1) {
389             throw new RuntimeException("The code defines more than one class");
390         }
391         return (ClassNode) classes.get(0);
392     }
393 
394     /***
395      * Takes a field definition statement and wrap it in class definition. The FieldNode object
396      * representing the field is extracted and returned, Types need to be fully qualified.
397      *
398      * @param code a naked statement to define a field, such as: String prop = "hello"
399      * @return a FieldNode object.
400      * @throws CompilationFailedException
401      */
402     public static FieldNode createFieldNode(String code) throws CompilationFailedException {
403         ClassNode classNode = createClassNode(wrapCode(code));
404         List flds = classNode.getFields();
405         if (flds.size() > 1) {
406             throw new RuntimeException("The code defines more than one field");
407         }
408         return (FieldNode) flds.get(0);
409     }
410 
411     public Statement createStatement(String code) throws CompilationFailedException {
412         ModuleNode module = createModule(code);
413         BlockStatement block = module.getStatementBlock();
414         if (block == null) {
415             throw new RuntimeException("no proper statement block is created.");
416         }
417         List stats = block.getStatements();
418         if (stats == null || stats.size() != 1) {
419             throw new RuntimeException("no proper statement node is created.");
420         }
421         return (Statement) stats.get(0);
422     }
423 
424     public MethodNode createMethodNode(String code) throws CompilationFailedException {
425         code = code.trim();
426         if (code.indexOf("def") != 0) {
427             code = "def " + code;
428         }
429         ModuleNode module = createModule(code);
430         List ms = module.getMethods();
431         if (ms == null || ms.size() != 1) {
432             throw new RuntimeException("no proper method node is created.");
433         }
434         return (MethodNode) ms.get(0);
435     }
436 
437     private static String wrapCode(String code) {
438         String prefix = "class SynthedClass {\n";
439         String suffix = "\n }";
440         return prefix + code + suffix;
441 
442     }
443     
444     public void addException(Exception e) throws CompilationFailedException {
445         getErrorCollector().addException(e,this);
446     }
447     
448     public void addError(SyntaxException se) throws CompilationFailedException {
449         getErrorCollector().addError(se,this);
450     }
451 }
452 
453 
454 
455