View Javadoc

1   /*
2    $Id: SourceUnit.java,v 1.2 2004/07/10 03:31:41 bran 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.IOException;
51  import java.io.Reader;
52  import java.lang.reflect.Constructor;
53  import java.lang.reflect.InvocationTargetException;
54  import java.net.URL;
55  import java.util.List;
56  
57  import org.codehaus.groovy.GroovyBugError;
58  import org.codehaus.groovy.ast.ClassNode;
59  import org.codehaus.groovy.ast.FieldNode;
60  import org.codehaus.groovy.ast.MethodNode;
61  import org.codehaus.groovy.ast.ModuleNode;
62  import org.codehaus.groovy.ast.stmt.BlockStatement;
63  import org.codehaus.groovy.ast.stmt.Statement;
64  import org.codehaus.groovy.control.io.FileReaderSource;
65  import org.codehaus.groovy.control.io.ReaderSource;
66  import org.codehaus.groovy.control.io.StringReaderSource;
67  import org.codehaus.groovy.control.io.URLReaderSource;
68  import org.codehaus.groovy.control.messages.LocatedMessage;
69  import org.codehaus.groovy.control.messages.Message;
70  import org.codehaus.groovy.control.messages.SimpleMessage;
71  import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
72  import org.codehaus.groovy.control.messages.WarningMessage;
73  import org.codehaus.groovy.syntax.CSTNode;
74  import org.codehaus.groovy.syntax.Reduction;
75  import org.codehaus.groovy.syntax.SyntaxException;
76  import org.codehaus.groovy.syntax.Token;
77  import org.codehaus.groovy.syntax.TokenStream;
78  import org.codehaus.groovy.syntax.Types;
79  import org.codehaus.groovy.syntax.lexer.GroovyLexer;
80  import org.codehaus.groovy.syntax.lexer.LexerTokenStream;
81  import org.codehaus.groovy.syntax.lexer.ReaderCharStream;
82  import org.codehaus.groovy.syntax.parser.ASTBuilder;
83  import org.codehaus.groovy.syntax.parser.Parser;
84  import org.codehaus.groovy.syntax.parser.UnexpectedTokenException;
85  import org.codehaus.groovy.tools.Utilities;
86  
87  
88  
89  /***
90   *  Provides an anchor for a single source unit (usually a script file)
91   *  as it passes through the compiler system.
92   *
93   *  @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
94   *  @author <a href="mailto:b55r@sina.com">Bing Ran</a>
95   *
96   *  @version $Id: SourceUnit.java,v 1.2 2004/07/10 03:31:41 bran Exp $
97   */
98  
99  public class SourceUnit extends ProcessingUnit
100 {
101     
102   //---------------------------------------------------------------------------
103   // CONSTRUCTION AND SUCH
104     
105     protected ReaderSource source;    // Where we can get Readers for our source unit
106     protected String       name;      // A descriptive name of the source unit
107     protected Reduction    cst;       // A Concrete Syntax Tree of the source
108     protected ModuleNode   ast;       // The root of the Abstract Syntax Tree for the source
109     
110 
111     
112    /***
113     *  Initializes the SourceUnit from existing machinery.
114     */
115     
116     public SourceUnit( String name, ReaderSource source, CompilerConfiguration flags, ClassLoader loader ) 
117     {
118         super( flags, loader );
119         
120         this.name   = name;
121         this.source = source;
122     }
123     
124     
125 
126    /***
127     *  Initializes the SourceUnit from the specified file.
128     */
129     
130     public SourceUnit( File source, CompilerConfiguration configuration, ClassLoader loader )
131     {
132         this( source.getPath(), new FileReaderSource(source, configuration), configuration, loader );
133     }
134     
135 
136    /***
137     *  Initializes the SourceUnit from the specified URL.
138     */
139     
140     public SourceUnit( URL source, CompilerConfiguration configuration, ClassLoader loader )
141     {
142         this( source.getPath(), new URLReaderSource(source, configuration), configuration, loader );
143     }
144     
145     
146    
147    /***
148     *  Initializes the SourceUnit for a string of source.
149     */
150     
151     public SourceUnit( String name, String source, CompilerConfiguration configuration, ClassLoader loader )
152     {
153         this( name, new StringReaderSource(source, configuration), configuration, loader );
154     }
155     
156     
157    /***
158     *  Returns the name for the SourceUnit.
159     */
160     
161     public String getName()
162     {
163         return name;
164     }
165     
166     
167     
168    /***
169     *  Returns the Concrete Syntax Tree produced during parse()ing.
170     */
171     
172     public Reduction getCST()
173     {
174         return this.cst;
175     }
176     
177     
178     
179    /***
180     *  Returns the Abstract Syntax Tree produced during parse()ing
181     *  and expanded during later phases.
182     */
183     
184     public ModuleNode getAST()
185     {
186         return this.ast;
187     }
188     
189     
190     
191    /***
192     *  Convenience routine, primarily for use by the InteractiveShell,
193     *  that returns true if parse() failed with an unexpected EOF.
194     */
195     
196     public boolean failedWithUnexpectedEOF()
197     {
198         boolean result = false;
199         
200         if( this.errors != null )
201         {
202             Message last = (Message)errors.get(errors.size() - 1);
203             if( last instanceof SyntaxErrorMessage ) 
204             {
205                 SyntaxException cause = ((SyntaxErrorMessage)last).getCause();
206                 if( cause instanceof UnexpectedTokenException ) 
207                 {
208                     Token unexpected = ((UnexpectedTokenException)cause).getUnexpectedToken();
209                     if( unexpected.isA(Types.EOF) )
210                     {
211                         result = true;
212                     }
213                 }
214             }
215         }
216         
217         return result;
218     }
219 
220 
221     
222   //---------------------------------------------------------------------------
223   // FACTORIES
224 
225     
226    /***
227     *  A convenience routine to create a standalone SourceUnit on a String 
228     *  with defaults for almost everything that is configurable. 
229     */
230     
231     public static SourceUnit create( String name, String source )
232     {
233         CompilerConfiguration configuration = new CompilerConfiguration();
234         configuration.setTolerance( 1 );
235         
236         return new SourceUnit( name, source, configuration, null );
237     }
238     
239 
240     
241    /***
242     *  A convenience routine to create a standalone SourceUnit on a String 
243     *  with defaults for almost everything that is configurable. 
244     */
245      
246     public static SourceUnit create( String name, String source, int tolerance )
247     {
248         CompilerConfiguration configuration = new CompilerConfiguration();
249         configuration.setTolerance( tolerance );
250         
251         return new SourceUnit( name, source, configuration, null );
252     }
253      
254 
255      
256      
257     
258   //---------------------------------------------------------------------------
259   // PROCESSING
260 
261     
262    /***
263     *  Parses the source to a CST.  You can retrieve it with getCST().
264     */
265     
266     public void parse() throws CompilationFailedException
267     {
268         if( this.phase > Phases.PARSING )
269         {
270             throw new GroovyBugError( "parsing is already complete" );
271         }
272         
273         if( this.phase == Phases.INITIALIZATION )
274         {
275             nextPhase();
276         }
277     
278         
279         //
280         // Create a reader on the source and run the parser.
281         
282         Reader reader = null;
283         try
284         {
285             reader = source.getReader();
286 
287             //
288             // Create a lexer and token stream
289 
290             GroovyLexer lexer  = new GroovyLexer( new ReaderCharStream(reader) );
291             TokenStream stream = new LexerTokenStream( lexer );
292             
293             //
294             // Do the parsing
295             
296             Parser parser = new Parser( this, stream );
297             this.cst = parser.parse();
298             
299             completePhase();
300         }
301         catch( IOException e )
302         {
303             addFatalError( new SimpleMessage(e.getMessage()) );
304         }
305         finally
306         {
307             if( reader != null )
308             {
309                 try { reader.close(); } catch( IOException e ) {}
310             }
311         }
312     }
313     
314     
315     
316    /***
317     *  Generates an AST from the CST.  You can retrieve it with getAST().
318     */
319 
320     public void convert() throws CompilationFailedException
321     {
322         if( this.phase == Phases.PARSING && this.phaseComplete )
323         {
324             gotoPhase( Phases.CONVERSION );
325         }
326         
327         if( this.phase != Phases.CONVERSION )
328         {
329             throw new GroovyBugError( "SourceUnit not ready for convert()" );
330         }
331 
332         
333         //
334         // Build the AST
335         
336         try
337         {
338             ASTBuilder builder = new ASTBuilder( this, this.classLoader );
339             this.ast = builder.build( this.cst );
340             this.ast.setDescription( this.name );
341         }
342         catch( SyntaxException e )
343         {
344             addError( new SyntaxErrorMessage(e) );
345         }
346         
347         completePhase();
348     }
349     
350     
351 
352     
353   //---------------------------------------------------------------------------
354   // ERROR REPORTING
355 
356     
357    /***
358     *  Convenience wrapper for addWarning() that won't create an object
359     *  unless it is relevant.
360     */
361      
362     public void addWarning( int importance, String text, CSTNode context )
363     {
364         if( WarningMessage.isRelevant(importance, this.warningLevel) )
365         {
366             addWarning( new WarningMessage(importance, text, context) );
367         }
368     }
369      
370     
371     
372     /***
373      *  Convenience wrapper for addWarning() that won't create an object
374      *  unless it is relevant.
375      */
376       
377     public void addWarning( int importance, String text, Object data, CSTNode context )
378     {
379         if( WarningMessage.isRelevant(importance, this.warningLevel) )
380         {
381             addWarning( new WarningMessage(importance, text, data, context) );
382         }
383     }
384 
385      
386      
387    /***
388     *  Convenience wrapper for addError().
389     */
390     
391     public void addError( SyntaxException error ) throws CompilationFailedException
392     {
393         addError( Message.create(error), error.isFatal() );
394     }
395     
396     
397     
398    /***
399     *  Convenience wrapper for addError().
400     */
401     
402     public void addError( String text, CSTNode context ) throws CompilationFailedException
403     {
404         addError( new LocatedMessage(text, context) );
405     }
406 
407     
408     
409     
410   //---------------------------------------------------------------------------
411   // SOURCE SAMPLING
412 
413     
414    /***
415     *  Returns a sampling of the source at the specified line and column,
416     *  of null if it is unavailable.
417     */
418     
419     public String getSample( int line, int column, Janitor janitor )
420     {
421         String sample = null;
422         String text   = source.getLine( line, janitor );
423 
424         if( text != null )
425         {
426             if( column > 0 )
427             {
428                 String marker = Utilities.repeatString(" ", column-1) + "^";
429 
430                 if( column > 40 )
431                 {
432                     int start = column - 30 - 1;
433                     int end   = (column + 10 > text.length() ? text.length() : column + 10 - 1);
434                     sample = "   " + text.substring( start, end ) + Utilities.eol() + "   " + marker.substring( start, marker.length() );
435                 }
436                 else
437                 {
438                     sample = "   " + text + Utilities.eol() + "   " + marker;
439                 }
440             }
441             else
442             {
443                 sample = text;
444             }
445         }
446 
447         return sample;
448     }
449     
450     /***
451      * to quickly create a ModuleNode from a piece of Groovy code
452      * @param code
453      * @return
454      * @throws CompilationFailedException
455      */
456     public static ModuleNode createModule(String code) throws CompilationFailedException {
457         SourceUnit su = create("NodeGen", code);
458         su.parse();
459         su.convert();
460         return su.getAST();
461     }
462     
463     public static ClassNode createClassNode(String code) throws CompilationFailedException {
464     	ModuleNode module = createModule(code);
465     	List classes = module.getClasses();
466     	if (classes.size() > 1) {
467     		throw new RuntimeException("The code defines more than one class");
468     	}
469     	return (ClassNode) classes.get(0);
470     }
471 
472     /***
473      * Takes a field definition statement and wrap it in class definition. The FieldNode object
474      * representing the field is extracted and returned, Types need to be fully qualified. 
475      * @param code a naked statement to define a field, such as: String prop = "hello"
476      * @return a FieldNode object. 
477      * @throws CompilationFailedException
478      */
479     public static FieldNode createFieldNode(String code) throws CompilationFailedException {
480     	ClassNode classNode = createClassNode(wrapCode(code));
481     	List flds = classNode.getFields();
482     	if (flds.size() > 1) 
483     		throw new RuntimeException("The code defines more than one field");    		
484     	return (FieldNode) flds.get(0);
485     }
486     
487 	public Statement createStatement(String code) throws CompilationFailedException {
488 		ModuleNode module = createModule(code);
489 		BlockStatement block = module.getStatementBlock();
490 		if (block == null)
491 			throw new RuntimeException("no proper statement block is created.");
492 		List stats = block.getStatements();
493 		if (stats == null || stats.size() != 1)
494 			throw new RuntimeException("no proper statement node is created.");
495 		return (Statement)stats.get(0);
496 	}
497 	
498 	public MethodNode createMethodNode(String code) throws CompilationFailedException {
499 		code  = code.trim();
500 		if (code.indexOf("def") != 0) {
501 			code = "def " + code;
502 		}
503 		ModuleNode module = createModule(code);
504 		List ms = module.getMethods();
505 		if (ms == null || ms.size() != 1)
506 			throw new RuntimeException("no proper method node is created.");
507 		return (MethodNode)ms.get(0);
508 	}
509 	
510 	private static String wrapCode(String code) {
511 		String prefix = "class SynthedClass {\n";
512 		String suffix = "\n }";
513 		return prefix + code + suffix;
514 			
515 	}
516 }
517 
518 
519 
520