View Javadoc

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