001 /* 002 $Id: SourceUnit.java 4098 2006-10-10 16:09:48Z blackdrag $ 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 groovy.lang.GroovyClassLoader; 050 051 import java.io.File; 052 import java.io.FileWriter; 053 import java.io.IOException; 054 import java.io.Reader; 055 import java.net.URL; 056 import java.security.AccessController; 057 import java.security.PrivilegedAction; 058 059 import org.codehaus.groovy.GroovyBugError; 060 import org.codehaus.groovy.ast.ModuleNode; 061 import org.codehaus.groovy.control.io.FileReaderSource; 062 import org.codehaus.groovy.control.io.ReaderSource; 063 import org.codehaus.groovy.control.io.StringReaderSource; 064 import org.codehaus.groovy.control.io.URLReaderSource; 065 import org.codehaus.groovy.control.messages.Message; 066 import org.codehaus.groovy.control.messages.SimpleMessage; 067 import org.codehaus.groovy.control.messages.SyntaxErrorMessage; 068 import org.codehaus.groovy.syntax.*; 069 import org.codehaus.groovy.tools.Utilities; 070 071 import antlr.CharScanner; 072 import antlr.MismatchedTokenException; 073 import antlr.NoViableAltException; 074 import antlr.NoViableAltForCharException; 075 076 import com.thoughtworks.xstream.XStream; 077 078 079 /** 080 * Provides an anchor for a single source unit (usually a script file) 081 * as it passes through the compiler system. 082 * 083 * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a> 084 * @author <a href="mailto:b55r@sina.com">Bing Ran</a> 085 * @version $Id: SourceUnit.java 4098 2006-10-10 16:09:48Z blackdrag $ 086 */ 087 088 public class SourceUnit extends ProcessingUnit { 089 090 /** 091 * The pluggable parser used to generate the AST - we allow pluggability currently as we need to have Classic and JSR support 092 */ 093 private ParserPlugin parserPlugin; 094 095 /** 096 * Where we can get Readers for our source unit 097 */ 098 protected ReaderSource source; 099 /** 100 * A descriptive name of the source unit. This name shouldn't 101 * be used for controling the SourceUnit, it is only for error 102 * messages 103 */ 104 protected String name; 105 /** 106 * A Concrete Syntax Tree of the source 107 */ 108 protected Reduction cst; 109 110 /** 111 * A facade over the CST 112 */ 113 protected SourceSummary sourceSummary; 114 /** 115 * The root of the Abstract Syntax Tree for the source 116 */ 117 protected ModuleNode ast; 118 119 120 /** 121 * Initializes the SourceUnit from existing machinery. 122 */ 123 public SourceUnit(String name, ReaderSource source, CompilerConfiguration flags, GroovyClassLoader loader, ErrorCollector er) { 124 super(flags, loader, er); 125 126 this.name = name; 127 this.source = source; 128 } 129 130 131 /** 132 * Initializes the SourceUnit from the specified file. 133 */ 134 public SourceUnit(File source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) { 135 this(source.getPath(), new FileReaderSource(source, configuration), configuration, loader, er); 136 } 137 138 139 /** 140 * Initializes the SourceUnit from the specified URL. 141 */ 142 public SourceUnit(URL source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) { 143 this(source.getPath(), new URLReaderSource(source, configuration), configuration, loader, er); 144 } 145 146 147 /** 148 * Initializes the SourceUnit for a string of source. 149 */ 150 public SourceUnit(String name, String source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) { 151 this(name, new StringReaderSource(source, configuration), configuration, loader, er); 152 } 153 154 155 /** 156 * Returns the name for the SourceUnit. This name shouldn't 157 * be used for controling the SourceUnit, it is only for error 158 * messages 159 */ 160 public String getName() { 161 return name; 162 } 163 164 165 /** 166 * Returns the Concrete Syntax Tree produced during parse()ing. 167 */ 168 public Reduction getCST() { 169 return this.cst; 170 } 171 172 /** 173 * Returns the Source Summary 174 */ 175 public SourceSummary getSourceSummary() { 176 return this.sourceSummary; 177 } 178 /** 179 * Returns the Abstract Syntax Tree produced during parse()ing 180 * and expanded during later phases. 181 */ 182 public ModuleNode getAST() { 183 return this.ast; 184 } 185 186 187 /** 188 * Convenience routine, primarily for use by the InteractiveShell, 189 * that returns true if parse() failed with an unexpected EOF. 190 */ 191 public boolean failedWithUnexpectedEOF() { 192 // Implementation note - there are several ways for the Groovy compiler 193 // to report an unexpected EOF. Perhaps this implementation misses some. 194 // If you find another way, please add it. 195 if (getErrorCollector().hasErrors()) { 196 Message last = (Message) getErrorCollector().getLastError(); 197 Throwable cause = null; 198 if (last instanceof SyntaxErrorMessage) { 199 cause = ((SyntaxErrorMessage) last).getCause().getCause(); 200 } 201 if (cause != null) { 202 if (cause instanceof NoViableAltException) { 203 return isEofToken(((NoViableAltException) cause).token); 204 } else if (cause instanceof NoViableAltForCharException) { 205 char badChar = ((NoViableAltForCharException) cause).foundChar; 206 return badChar == CharScanner.EOF_CHAR; 207 } else if (cause instanceof MismatchedTokenException) { 208 return isEofToken(((MismatchedTokenException) cause).token); 209 } 210 } 211 } 212 return false; 213 } 214 215 protected boolean isEofToken(antlr.Token token) { 216 return token.getType() == antlr.Token.EOF_TYPE; 217 } 218 219 220 221 //--------------------------------------------------------------------------- 222 // FACTORIES 223 224 225 /** 226 * A convenience routine to create a standalone SourceUnit on a String 227 * with defaults for almost everything that is configurable. 228 */ 229 public static SourceUnit create(String name, String source) { 230 CompilerConfiguration configuration = new CompilerConfiguration(); 231 configuration.setTolerance(1); 232 233 return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration)); 234 } 235 236 237 /** 238 * A convenience routine to create a standalone SourceUnit on a String 239 * with defaults for almost everything that is configurable. 240 */ 241 public static SourceUnit create(String name, String source, int tolerance) { 242 CompilerConfiguration configuration = new CompilerConfiguration(); 243 configuration.setTolerance(tolerance); 244 245 return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration)); 246 } 247 248 249 250 251 252 //--------------------------------------------------------------------------- 253 // PROCESSING 254 255 256 /** 257 * Parses the source to a CST. You can retrieve it with getCST(). 258 */ 259 public void parse() throws CompilationFailedException { 260 if (this.phase > Phases.PARSING) { 261 throw new GroovyBugError("parsing is already complete"); 262 } 263 264 if (this.phase == Phases.INITIALIZATION) { 265 nextPhase(); 266 } 267 268 269 // 270 // Create a reader on the source and run the parser. 271 272 Reader reader = null; 273 try { 274 reader = source.getReader(); 275 276 // lets recreate the parser each time as it tends to keep around state 277 parserPlugin = getConfiguration().getPluginFactory().createParserPlugin(); 278 279 cst = parserPlugin.parseCST(this, reader); 280 sourceSummary = parserPlugin.getSummary(); 281 282 reader.close(); 283 284 } 285 catch (IOException e) { 286 getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(),this)); 287 } 288 finally { 289 if (reader != null) { 290 try { 291 reader.close(); 292 } 293 catch (IOException e) { 294 } 295 } 296 } 297 } 298 299 300 /** 301 * Generates an AST from the CST. You can retrieve it with getAST(). 302 */ 303 public void convert() throws CompilationFailedException { 304 if (this.phase == Phases.PARSING && this.phaseComplete) { 305 gotoPhase(Phases.CONVERSION); 306 } 307 308 if (this.phase != Phases.CONVERSION) { 309 throw new GroovyBugError("SourceUnit not ready for convert()"); 310 } 311 312 313 // 314 // Build the AST 315 316 try { 317 this.ast = parserPlugin.buildAST(this, this.classLoader, this.cst); 318 319 this.ast.setDescription(this.name); 320 } 321 catch (SyntaxException e) { 322 getErrorCollector().addError(new SyntaxErrorMessage(e,this)); 323 } 324 325 String property = (String) AccessController.doPrivileged(new PrivilegedAction() { 326 public Object run() { 327 return System.getProperty("groovy.ast"); 328 } 329 }); 330 331 if ("xml".equals(property)) { 332 saveAsXML(name,ast); 333 } 334 } 335 336 private void saveAsXML(String name, ModuleNode ast) { 337 XStream xstream = new XStream(); 338 try { 339 xstream.toXML(ast,new FileWriter(name + ".xml")); 340 System.out.println("Written AST to " + name + ".xml"); 341 } catch (Exception e) { 342 System.out.println("Couldn't write to " + name + ".xml"); 343 e.printStackTrace(); 344 } 345 } 346 //--------------------------------------------------------------------------- // SOURCE SAMPLING 347 348 /** 349 * Returns a sampling of the source at the specified line and column, 350 * of null if it is unavailable. 351 */ 352 public String getSample(int line, int column, Janitor janitor) { 353 String sample = null; 354 String text = source.getLine(line, janitor); 355 356 if (text != null) { 357 if (column > 0) { 358 String marker = Utilities.repeatString(" ", column - 1) + "^"; 359 360 if (column > 40) { 361 int start = column - 30 - 1; 362 int end = (column + 10 > text.length() ? text.length() : column + 10 - 1); 363 sample = " " + text.substring(start, end) + Utilities.eol() + " " + marker.substring(start, marker.length()); 364 } 365 else { 366 sample = " " + text + Utilities.eol() + " " + marker; 367 } 368 } 369 else { 370 sample = text; 371 } 372 } 373 374 return sample; 375 } 376 377 public void addException(Exception e) throws CompilationFailedException { 378 getErrorCollector().addException(e,this); 379 } 380 381 public void addError(SyntaxException se) throws CompilationFailedException { 382 getErrorCollector().addError(se,this); 383 } 384 } 385 386 387 388