001 /*
002 $Id: SourceUnit.java,v 1.12 2005/06/10 09:55:30 cstein Exp $
003
004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005
006 Redistribution and use of this software and associated documentation
007 ("Software"), with or without modification, are permitted provided
008 that the following conditions are met:
009
010 1. Redistributions of source code must retain copyright
011 statements and notices. Redistributions must also contain a
012 copy of this document.
013
014 2. Redistributions in binary form must reproduce the
015 above copyright notice, this list of conditions and the
016 following disclaimer in the documentation and/or other
017 materials provided with the distribution.
018
019 3. The name "groovy" must not be used to endorse or promote
020 products derived from this Software without prior written
021 permission of The Codehaus. For written permission,
022 please contact info@codehaus.org.
023
024 4. Products derived from this Software may not be called "groovy"
025 nor may "groovy" appear in their names without prior written
026 permission of The Codehaus. "groovy" is a registered
027 trademark of The Codehaus.
028
029 5. Due credit should be given to The Codehaus -
030 http://groovy.codehaus.org/
031
032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043 OF THE POSSIBILITY OF SUCH DAMAGE.
044
045 */
046
047 package org.codehaus.groovy.control;
048
049 import java.io.File;
050 import java.io.FileWriter;
051 import java.io.IOException;
052 import java.io.Reader;
053 import java.net.URL;
054 import java.util.List;
055
056 import org.codehaus.groovy.GroovyBugError;
057 import org.codehaus.groovy.ast.ClassNode;
058 import org.codehaus.groovy.ast.FieldNode;
059 import org.codehaus.groovy.ast.MethodNode;
060 import org.codehaus.groovy.ast.ModuleNode;
061 import org.codehaus.groovy.ast.stmt.BlockStatement;
062 import org.codehaus.groovy.ast.stmt.Statement;
063 import org.codehaus.groovy.control.io.FileReaderSource;
064 import org.codehaus.groovy.control.io.ReaderSource;
065 import org.codehaus.groovy.control.io.StringReaderSource;
066 import org.codehaus.groovy.control.io.URLReaderSource;
067 import org.codehaus.groovy.control.messages.ExceptionMessage;
068 import org.codehaus.groovy.control.messages.Message;
069 import org.codehaus.groovy.control.messages.SimpleMessage;
070 import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
071 import org.codehaus.groovy.syntax.Reduction;
072 import org.codehaus.groovy.syntax.SyntaxException;
073 import org.codehaus.groovy.syntax.Token;
074 import org.codehaus.groovy.syntax.Types;
075 import org.codehaus.groovy.syntax.UnexpectedTokenException;
076 import org.codehaus.groovy.tools.Utilities;
077
078 import antlr.MismatchedTokenException;
079 import antlr.NoViableAltException;
080
081 import com.thoughtworks.xstream.XStream;
082
083
084 /**
085 * Provides an anchor for a single source unit (usually a script file)
086 * as it passes through the compiler system.
087 *
088 * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
089 * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
090 * @version $Id: SourceUnit.java,v 1.12 2005/06/10 09:55:30 cstein Exp $
091 */
092
093 public class SourceUnit extends ProcessingUnit {
094
095 /**
096 * The pluggable parser used to generate the AST - we allow pluggability currently as we need to have Classic and JSR support
097 */
098 private ParserPlugin parserPlugin;
099
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 // Classic support
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 // JSR support
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 // FACTORIES
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 // PROCESSING
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 // Create a reader on the source and run the parser.
269
270 Reader reader = null;
271 try {
272 reader = source.getReader();
273
274 // lets recreate the parser each time as it tends to keep around state
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 // Build the AST
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 //--------------------------------------------------------------------------- // SOURCE SAMPLING
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