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