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.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
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
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
260
261 Reader reader = null;
262 try {
263 reader = source.getReader();
264
265
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
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
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
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