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