001    /*
002     $Id: SourceUnit.java,v 1.14 2005/11/13 16:42:11 blackdrag 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 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    
057    import org.codehaus.groovy.GroovyBugError;
058    import org.codehaus.groovy.ast.ModuleNode;
059    import org.codehaus.groovy.control.io.FileReaderSource;
060    import org.codehaus.groovy.control.io.ReaderSource;
061    import org.codehaus.groovy.control.io.StringReaderSource;
062    import org.codehaus.groovy.control.io.URLReaderSource;
063    import org.codehaus.groovy.control.messages.ExceptionMessage;
064    import org.codehaus.groovy.control.messages.Message;
065    import org.codehaus.groovy.control.messages.SimpleMessage;
066    import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
067    import org.codehaus.groovy.syntax.*;
068    import org.codehaus.groovy.tools.Utilities;
069    
070    import antlr.MismatchedTokenException;
071    import antlr.NoViableAltException;
072    
073    import com.thoughtworks.xstream.XStream;
074    
075    
076    /**
077     * Provides an anchor for a single source unit (usually a script file)
078     * as it passes through the compiler system.
079     *
080     * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
081     * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
082     * @version $Id: SourceUnit.java,v 1.14 2005/11/13 16:42:11 blackdrag Exp $
083     */
084    
085    public class SourceUnit extends ProcessingUnit {
086    
087        /**
088         * The pluggable parser used to generate the AST - we allow pluggability currently as we need to have Classic and JSR support
089         */
090        private ParserPlugin parserPlugin;
091    
092        /**
093         * Where we can get Readers for our source unit
094         */
095        protected ReaderSource source;
096        /**
097         * A descriptive name of the source unit
098         */
099        protected String name;
100        /**
101         * A Concrete Syntax Tree of the source
102         */
103        protected Reduction cst;
104    
105        /**
106         * A facade over the CST
107         */
108        protected SourceSummary sourceSummary;
109        /**
110         * The root of the Abstract Syntax Tree for the source
111         */
112        protected ModuleNode ast;
113    
114    
115        /**
116         * Initializes the SourceUnit from existing machinery.
117         */
118        public SourceUnit(String name, ReaderSource source, CompilerConfiguration flags, GroovyClassLoader loader, ErrorCollector er) {
119            super(flags, loader, er);
120    
121            this.name = name;
122            this.source = source;
123        }
124    
125    
126        /**
127         * Initializes the SourceUnit from the specified file.
128         */
129        public SourceUnit(File source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
130            this(source.getPath(), new FileReaderSource(source, configuration), configuration, loader, er);
131        }
132    
133    
134        /**
135         * Initializes the SourceUnit from the specified URL.
136         */
137        public SourceUnit(URL source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
138            this(source.getPath(), new URLReaderSource(source, configuration), configuration, loader, er);
139        }
140    
141    
142        /**
143         * Initializes the SourceUnit for a string of source.
144         */
145        public SourceUnit(String name, String source, CompilerConfiguration configuration, GroovyClassLoader loader, ErrorCollector er) {
146            this(name, new StringReaderSource(source, configuration), configuration, loader, er);
147        }
148    
149    
150        /**
151         * Returns the name for the SourceUnit.
152         */
153        public String getName() {
154            return name;
155        }
156    
157    
158        /**
159         * Returns the Concrete Syntax Tree produced during parse()ing.
160         */
161        public Reduction getCST() {
162            return this.cst;
163        }
164    
165        /**
166         * Returns the Source Summary
167         */
168        public SourceSummary getSourceSummary() {
169            return this.sourceSummary;
170        }
171        /**
172         * Returns the Abstract Syntax Tree produced during parse()ing
173         * and expanded during later phases.
174         */
175        public ModuleNode getAST() {
176            return this.ast;
177        }
178    
179    
180        /**
181         * Convenience routine, primarily for use by the InteractiveShell,
182         * that returns true if parse() failed with an unexpected EOF.
183         */
184        public boolean failedWithUnexpectedEOF() {
185            boolean result = false;
186            if (getErrorCollector().hasErrors()) {
187                // Classic support
188                Message last = (Message) getErrorCollector().getLastError();
189                if (last instanceof SyntaxErrorMessage) {
190                    SyntaxException cause = ((SyntaxErrorMessage) last).getCause();
191                    if (cause instanceof UnexpectedTokenException) {
192                        Token unexpected = ((UnexpectedTokenException) cause).getUnexpectedToken();
193                        if (unexpected.isA(Types.EOF)) {
194                            result = true;
195                        }
196                    }
197                }
198                // JSR support
199                if (last instanceof ExceptionMessage) {
200                    ExceptionMessage exceptionMessage = (ExceptionMessage) last;
201                    Exception cause = exceptionMessage.getCause();
202                    if (cause instanceof NoViableAltException) {
203                        NoViableAltException antlrException = (NoViableAltException) cause;
204                        result = isEofToken(antlrException.token);
205                    }
206                    if (cause instanceof MismatchedTokenException) {
207                        MismatchedTokenException antlrException = (MismatchedTokenException) cause;
208                        result = isEofToken(antlrException.token);
209                    }
210                }
211            }
212            return result;    
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            if ("xml".equals(System.getProperty("groovy.ast"))) {
326                saveAsXML(name,ast);
327            }
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        public void addException(Exception e) throws CompilationFailedException {
372            getErrorCollector().addException(e,this);
373        }
374        
375        public void addError(SyntaxException se) throws CompilationFailedException {
376            getErrorCollector().addError(se,this);
377        }
378    }
379    
380    
381    
382