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