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 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
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
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
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
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
287
288 Reader reader = null;
289 try {
290 reader = source.getReader();
291
292
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
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
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
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