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