1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
104
105 protected ReaderSource source;
106 protected String name;
107 protected Reduction cst;
108 protected ModuleNode ast;
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
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
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
281
282 Reader reader = null;
283 try
284 {
285 reader = source.getReader();
286
287
288
289
290 GroovyLexer lexer = new GroovyLexer( new ReaderCharStream(reader) );
291 TokenStream stream = new LexerTokenStream( lexer );
292
293
294
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
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
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
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