/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.codehaus.groovy.control; import antlr.CharScanner; import antlr.MismatchedCharException; import antlr.MismatchedTokenException; import antlr.NoViableAltException; import antlr.NoViableAltForCharException; import groovy.lang.GroovyClassLoader; import org.codehaus.groovy.GroovyBugError; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.control.io.FileReaderSource; import org.codehaus.groovy.control.io.ReaderSource; import org.codehaus.groovy.control.io.StringReaderSource; import org.codehaus.groovy.control.io.URLReaderSource; import org.codehaus.groovy.control.messages.Message; import org.codehaus.groovy.control.messages.SimpleMessage; import org.codehaus.groovy.control.messages.SyntaxErrorMessage; import org.codehaus.groovy.syntax.Reduction; import org.codehaus.groovy.syntax.SyntaxException; import org.codehaus.groovy.tools.Utilities; import java.io.File; import java.io.IOException; import java.io.Reader; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; /** * Provides an anchor for a single source unit (usually a script file) * as it passes through the compiler system. * * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a> * @author <a href="mailto:b55r@sina.com">Bing Ran</a> */ public class SourceUnit extends ProcessingUnit { /** * The pluggable parser used to generate the AST - we allow * pluggability currently as we need to have Classic and JSR support */ private ParserPlugin parserPlugin; /** * Where we can get Readers for our source unit */ protected ReaderSource source; /** * A descriptive name of the source unit. This name shouldn't * be used for controlling the SourceUnit, it is only for error * messages and to determine the name of the class for * a script. */ protected String name; /** * A Concrete Syntax Tree of the source */ protected Reduction cst; /** * The root of the Abstract Syntax Tree for the source */ protected ModuleNode ast; /** * Initializes the SourceUnit from existing machinery. */ public SourceUnit(String name, ReaderSource source, CompilerConfiguration flags, GroovyClassLoader loader, ErrorCollector er) { super(flags, loader, er); this.name = name; this.source = source; } /** * Initializes the SourceUnit from the specified file. */ public SourceUnit(File source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) { this(source.getPath(), new FileReaderSource(source, configuration), configuration, loader, er); } /** * Initializes the SourceUnit from the specified URL. */ public SourceUnit(URL source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) { this(source.toExternalForm(), new URLReaderSource(source, configuration), configuration, loader, er); } /** * Initializes the SourceUnit for a string of source. */ public SourceUnit(String name, String source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) { this(name, new StringReaderSource(source, configuration), configuration, loader, er); } /** * Returns the name for the SourceUnit. This name shouldn't * be used for controlling the SourceUnit, it is only for error * messages */ public String getName() { return name; } /** * Returns the Concrete Syntax Tree produced during parse()ing. */ public Reduction getCST() { return this.cst; } /** * Returns the Abstract Syntax Tree produced during convert()ing * and expanded during later phases. */ public ModuleNode getAST() { return this.ast; } /** * Convenience routine, primarily for use by the InteractiveShell, * that returns true if parse() failed with an unexpected EOF. */ public boolean failedWithUnexpectedEOF() { // Implementation note - there are several ways for the Groovy compiler // to report an unexpected EOF. Perhaps this implementation misses some. // If you find another way, please add it. if (getErrorCollector().hasErrors()) { Message last = (Message) getErrorCollector().getLastError(); Throwable cause = null; if (last instanceof SyntaxErrorMessage) { cause = ((SyntaxErrorMessage) last).getCause().getCause(); } if (cause != null) { if (cause instanceof NoViableAltException) { return isEofToken(((NoViableAltException) cause).token); } else if (cause instanceof NoViableAltForCharException) { char badChar = ((NoViableAltForCharException) cause).foundChar; return badChar == CharScanner.EOF_CHAR; } else if (cause instanceof MismatchedCharException) { char badChar = (char) ((MismatchedCharException) cause).foundChar; return badChar == CharScanner.EOF_CHAR; } else if (cause instanceof MismatchedTokenException) { return isEofToken(((MismatchedTokenException) cause).token); } } } return false; } protected boolean isEofToken(antlr.Token token) { return token.getType() == antlr.Token.EOF_TYPE; } //--------------------------------------------------------------------------- // FACTORIES /** * A convenience routine to create a standalone SourceUnit on a String * with defaults for almost everything that is configurable. */ public static SourceUnit create(String name, String source) { CompilerConfiguration configuration = new CompilerConfiguration(); configuration.setTolerance(1); return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration)); } /** * A convenience routine to create a standalone SourceUnit on a String * with defaults for almost everything that is configurable. */ public static SourceUnit create(String name, String source, int tolerance) { CompilerConfiguration configuration = new CompilerConfiguration(); configuration.setTolerance(tolerance); return new SourceUnit(name, source, configuration, null, new ErrorCollector(configuration)); } //--------------------------------------------------------------------------- // PROCESSING /** * Parses the source to a CST. You can retrieve it with getCST(). */ public void parse() throws CompilationFailedException { if (this.phase > Phases.PARSING) { throw new GroovyBugError("parsing is already complete"); } if (this.phase == Phases.INITIALIZATION) { nextPhase(); } // // Create a reader on the source and run the parser. Reader reader = null; try { reader = source.getReader(); // let's recreate the parser each time as it tends to keep around state parserPlugin = getConfiguration().getPluginFactory().createParserPlugin(); cst = parserPlugin.parseCST(this, reader); reader.close(); } catch (IOException e) { getErrorCollector().addFatalError(new SimpleMessage(e.getMessage(), this)); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { // Ignore } } } } /** * Generates an AST from the CST. You can retrieve it with getAST(). */ public void convert() throws CompilationFailedException { if (this.phase == Phases.PARSING && this.phaseComplete) { gotoPhase(Phases.CONVERSION); } if (this.phase != Phases.CONVERSION) { throw new GroovyBugError("SourceUnit not ready for convert()"); } // // Build the AST try { this.ast = parserPlugin.buildAST(this, this.classLoader, this.cst); this.ast.setDescription(this.name); } catch (SyntaxException e) { if (this.ast == null) { // Create a dummy ModuleNode to represent a failed parse - in case a later phase attempts to use the ast this.ast = new ModuleNode(this); } getErrorCollector().addError(new SyntaxErrorMessage(e, this)); } String property = (String) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return System.getProperty("groovy.ast"); } }); if ("xml".equals(property)) { saveAsXML(name, ast); } } private static void saveAsXML(String name, ModuleNode ast) { XStreamUtils.serialize(name, ast); } //--------------------------------------------------------------------------- // SOURCE SAMPLING /** * Returns a sampling of the source at the specified line and column, * or null if it is unavailable. */ public String getSample(int line, int column, Janitor janitor) { String sample = null; String text = source.getLine(line, janitor); if (text != null) { if (column > 0) { String marker = Utilities.repeatString(" ", column - 1) + "^"; if (column > 40) { int start = column - 30 - 1; int end = (column + 10 > text.length() ? text.length() : column + 10 - 1); sample = " " + text.substring(start, end) + Utilities.eol() + " " + marker.substring(start, marker.length()); } else { sample = " " + text + Utilities.eol() + " " + marker; } } else { sample = text; } } return sample; } /** * This method adds an exception to the error collector. The Exception most likely has no line number attached to it. * For this reason you should use this method sparingly. Prefer using addError for syntax errors or add an error * to the {@link ErrorCollector} directly by retrieving it with getErrorCollector(). * @param e * the exception that occurred * @throws CompilationFailedException * on error */ public void addException(Exception e) throws CompilationFailedException { getErrorCollector().addException(e, this); } /** * This method adds a SyntaxException to the error collector. The exception should specify the line and column * number of the error. This method should be reserved for real errors in the syntax of the SourceUnit. If * your error is not in syntax, and is a semantic error, or more general error, then use addException or use * the error collector directly by retrieving it with getErrorCollector(). * @param se * the exception, which should have line and column information * @throws CompilationFailedException * on error */ public void addError(SyntaxException se) throws CompilationFailedException { getErrorCollector().addError(se, this); } public void addErrorAndContinue(SyntaxException se) throws CompilationFailedException { getErrorCollector().addErrorAndContinue(se, this); } public ReaderSource getSource() { return source; } }