View Javadoc

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