KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > ruby > RubyParser


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.netbeans.modules.ruby;
20
21 import java.io.IOException JavaDoc;
22 import java.io.Reader JavaDoc;
23 import java.io.StringReader JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.List JavaDoc;
26
27 import javax.swing.text.BadLocationException JavaDoc;
28
29 import org.jruby.ast.Node;
30 import org.jruby.ast.RootNode;
31 import org.jruby.common.IRubyWarnings;
32 import org.jruby.lexer.yacc.ISourcePosition;
33 import org.jruby.lexer.yacc.LexerSource;
34 import org.jruby.lexer.yacc.SyntaxException;
35 import org.jruby.parser.DefaultRubyParser;
36 import org.jruby.parser.RubyParserConfiguration;
37 import org.jruby.parser.RubyParserResult;
38 import org.netbeans.api.gsf.CompilationInfo;
39 import org.netbeans.api.gsf.Element;
40 import org.netbeans.api.gsf.Element;
41 import org.netbeans.api.gsf.ElementHandle;
42 import org.netbeans.api.gsf.Error;
43 import org.netbeans.api.gsf.ParseEvent;
44 import org.netbeans.api.gsf.ParseListener;
45 import org.netbeans.api.gsf.Parser;
46 import org.netbeans.api.gsf.ParserFile;
47 import org.netbeans.api.gsf.ParserResult;
48 import org.netbeans.api.gsf.PositionManager;
49 import org.netbeans.api.gsf.SemanticAnalyzer;
50 import org.netbeans.api.gsf.Severity;
51 import org.netbeans.api.gsf.Severity;
52 import org.netbeans.api.gsf.SourceFileReader;
53 import org.netbeans.editor.BaseDocument;
54 import org.netbeans.editor.Utilities;
55 import org.netbeans.modules.ruby.elements.AstElement;
56 import org.netbeans.modules.ruby.elements.AstRootElement;
57 import org.netbeans.modules.ruby.elements.IndexedElement;
58 import org.netbeans.modules.ruby.elements.KeywordElement;
59 import org.netbeans.spi.gsf.DefaultError;
60 import org.netbeans.spi.gsf.DefaultPosition;
61 import org.openide.filesystems.FileObject;
62 import org.openide.util.Exceptions;
63
64
65 /**
66  * Wrapper around JRuby to parse a buffer into an AST.
67  *
68  * @todo Rename to RubyParser for symmetry with RubyLexer
69  *
70  * @author Tor Norbye
71  */

72 public class RubyParser implements Parser {
73     private PositionManager positions = new RubyPositionManager();
74
75     /**
76      * Creates a new instance of RubyParser
77      */

78     public RubyParser() {
79     }
80
81     private static String JavaDoc asString(CharSequence JavaDoc sequence) {
82         if (sequence instanceof String JavaDoc) {
83             return (String JavaDoc)sequence;
84         } else {
85             return sequence.toString();
86         }
87     }
88
89     /** Parse the given set of files, and notify the parse listener for each transition
90      * (compilation results are attached to the events )
91      */

92     public void parseFiles(List JavaDoc<ParserFile> files, ParseListener listener, SourceFileReader reader) {
93         for (ParserFile file : files) {
94             ParseEvent beginEvent = new ParseEvent(ParseEvent.Kind.PARSE, file, null);
95             listener.started(beginEvent);
96
97             ParserResult result = null;
98
99             try {
100                 CharSequence JavaDoc buffer = reader.read(file);
101                 int offset = reader.getCaretOffset(file);
102                 result = parseBuffer(file, offset, buffer, listener, false);
103             } catch (IOException JavaDoc ioe) {
104                 listener.exception(ioe);
105             }
106
107             ParseEvent doneEvent = new ParseEvent(ParseEvent.Kind.PARSE, file, result);
108             listener.finished(doneEvent);
109         }
110     }
111
112     /**
113      * Try cleaning up the source buffer around the current offset to increase
114      * likelihood of parse success. Initially this method had a lot of
115      * logic to determine whether a parse was likely to fail (e.g. invoking
116      * the isEndMissing method from bracket completion etc.).
117      * However, I am now trying a parse with the real source first, and then
118      * only if that fails do I try parsing with sanitized source. Therefore,
119      * this method has to be less conservative in ripping out code since it
120      * will only be used when the regular source is failing.
121      */

122     private String JavaDoc getSanitizedSource(ParserFile file, int caretOffset, String JavaDoc buffer) {
123         // The user might be editing around the given caretOffset.
124
// See if it looks modified
125
// Insert an end statement? Insert a } marker?
126
FileObject fileObject = file.getFileObject();
127
128         if (fileObject == null) {
129             return buffer;
130         }
131
132         BaseDocument doc = AstUtilities.getBaseDocument(fileObject, false);
133
134         if (doc == null) {
135             return buffer;
136         }
137
138         if (caretOffset > doc.getLength()) {
139             return buffer;
140         }
141
142         try {
143             if (!(Utilities.isRowEmpty(doc, caretOffset) || Utilities.isRowWhite(doc, caretOffset))) {
144                 // See if I should try to remove the current line, since it has text on it.
145
int lineEnd = Utilities.getRowLastNonWhite(doc, caretOffset);
146
147                 if (lineEnd != -1) {
148                     StringBuilder JavaDoc sb = new StringBuilder JavaDoc(doc.getLength());
149                     int lineStart = Utilities.getRowStart(doc, caretOffset);
150                     int rest = lineStart + 1;
151
152                     sb.append(doc.getText(0, lineStart));
153                     sb.append('#');
154
155                     if (rest < doc.getLength()) {
156                         sb.append(doc.getText(rest, doc.getLength() - rest));
157                     }
158                     assert sb.length() == doc.getLength();
159
160                     return sb.toString();
161                 }
162             }
163         } catch (BadLocationException JavaDoc ble) {
164             Exceptions.printStackTrace(ble);
165         }
166
167         return buffer;
168     }
169
170     private void notifyError(ParseListener listener, ParserFile file, String JavaDoc key,
171         Severity severity, String JavaDoc description, String JavaDoc details, int offset) {
172         Error JavaDoc error =
173             new DefaultError(key, description, details, file.getFileObject(),
174                 new DefaultPosition(offset), new DefaultPosition(offset), severity);
175         listener.error(error);
176     }
177
178     public ParserResult parseBuffer(final ParserFile file, int caretOffset,
179         final CharSequence JavaDoc sequence, final ParseListener listener, boolean trySanitizing) {
180         String JavaDoc source = asString(sequence);
181         boolean sanitizedSource = false;
182
183         if (trySanitizing && (caretOffset != -1)) {
184             String JavaDoc s = getSanitizedSource(file, caretOffset, source);
185
186             if (s != source) {
187                 source = s;
188                 sanitizedSource = true;
189             }
190         }
191
192         Reader JavaDoc content = new StringReader JavaDoc(source);
193
194         RubyParserResult result = null;
195
196         final boolean ignoreErrors = sanitizedSource;
197
198         try {
199             IRubyWarnings warnings =
200                 new IRubyWarnings() {
201                     public void warn(ISourcePosition position, String JavaDoc message) {
202                         if (!ignoreErrors) {
203                             notifyError(listener, file, null, Severity.WARNING, message, null,
204                                 position.getStartOffset());
205                         }
206                     }
207
208                     public boolean isVerbose() {
209                         return false;
210                     }
211
212                     public void warn(String JavaDoc message) {
213                         if (!ignoreErrors) {
214                             notifyError(listener, file, null, Severity.WARNING, message, null, -1);
215                         }
216                     }
217
218                     public void warning(String JavaDoc message) {
219                         if (!ignoreErrors) {
220                             notifyError(listener, file, null, Severity.WARNING, message, null, -1);
221                         }
222                     }
223
224                     public void warning(ISourcePosition position, String JavaDoc message) {
225                         if (!ignoreErrors) {
226                             notifyError(listener, file, null, Severity.WARNING, message, null,
227                                 position.getStartOffset());
228                         }
229                     }
230                 };
231
232             //warnings.setFile(file);
233
DefaultRubyParser parser = new DefaultRubyParser();
234             parser.setWarnings(warnings);
235
236             String JavaDoc fileName = "";
237
238             if (file != null) {
239                 fileName = file.getFileObject().getNameExt();
240             }
241
242             LexerSource lexerSource = new LexerSource(fileName, content);
243             RubyParserConfiguration configuration = new RubyParserConfiguration();
244             result = parser.parse(configuration, lexerSource);
245         } catch (SyntaxException e) {
246             int offset = e.getPosition().getStartOffset();
247
248             // XXX should this be >, and = length?
249
if (offset >= source.length()) {
250                 offset = source.length() - 1;
251
252                 if (offset < 0) {
253                     offset = 0;
254                 }
255             }
256
257             if (!ignoreErrors) {
258                 notifyError(listener, file, null, Severity.ERROR, e.getMessage(),
259                     e.getLocalizedMessage(), offset);
260             }
261         }
262
263         Node root = (result != null) ? result.getAST() : null;
264
265         RootNode realRoot = null;
266
267         if (root instanceof RootNode) {
268             // Quick workaround for now to avoid NPEs all over when
269
// code looks at RootNode, whose getPosition()==null.
270
// Its bodynode is what used to be returned as the root!
271
realRoot = (RootNode)root;
272             root = realRoot.getBodyNode();
273         }
274
275         if (root != null) {
276             AstRootElement rootElement = new AstRootElement(file.getFileObject(), root, result);
277             AstNodeAdapter ast = new AstNodeAdapter(null, root);
278             RubyParseResult r = new RubyParseResult(file, rootElement, ast, root, realRoot, result);
279             r.setSanitizedSource(sanitizedSource);
280             r.setSource(source);
281
282             return r;
283         } else {
284             // Parsing failed -- see if we should try again with cleaned up source
285
// in case the user is in the middle of typing something, like "def"
286
if (!trySanitizing) {
287                 return parseBuffer(file, caretOffset, source, listener, true);
288             }
289
290             return new RubyParseResult(file);
291         }
292     }
293
294     public PositionManager getPositionManager() {
295         return positions;
296     }
297
298     public SemanticAnalyzer getSemanticAnalysisTask() {
299         return new SemanticAnalysis();
300     }
301
302     public org.netbeans.api.gsf.OccurrencesFinder getMarkOccurrencesTask(int caretPosition) {
303         OccurrencesFinder finder = new OccurrencesFinder();
304         finder.setCaretPosition(caretPosition);
305
306         return finder;
307     }
308
309     @SuppressWarnings JavaDoc("unchecked")
310     public <T extends Element> ElementHandle<T> createHandle(CompilationInfo info, final T object) {
311         if (object instanceof KeywordElement) {
312             // Not tied to an AST - just pass it around
313
return new RubyElementHandle(null, object);
314         }
315
316         // TODO - check for Ruby
317
if (object instanceof IndexedElement) {
318             // Probably a function in a "foreign" file (not parsed from AST),
319
// such as a signature returned from the index of the Ruby libraries.
320
return new RubyElementHandle(null, object);
321         }
322
323         if (!(object instanceof AstElement)) {
324             return null;
325         }
326
327         ParserResult result = info.getParserResult();
328
329         if (result == null) {
330             return null;
331         }
332
333         ParserResult.AstTreeNode ast = result.getAst();
334
335         if (ast == null) {
336             return null;
337         }
338
339         Node root = AstUtilities.getRoot(info);
340
341         return new RubyElementHandle(root, object);
342     }
343
344     @SuppressWarnings JavaDoc("unchecked")
345     public <T extends Element> T resolveHandle(CompilationInfo info, ElementHandle<T> handle) {
346         RubyElementHandle h = (RubyElementHandle)handle;
347         Node oldRoot = h.root;
348         Node oldNode;
349
350         if (h.object instanceof KeywordElement || h.object instanceof IndexedElement) {
351             // Not tied to a tree
352
return (T)h.object;
353         }
354
355         if (h.object instanceof AstElement) {
356             oldNode = ((AstElement)h.object).getNode(); // XXX Make it work for DefaultComObjects...
357
} else {
358             return null;
359         }
360
361         Node newRoot = AstUtilities.getRoot(info);
362
363         // Find newNode
364
Node newNode = find(oldRoot, oldNode, newRoot);
365
366         if (newNode != null) {
367             Element co = AstElement.create(newNode);
368
369             return (T)co;
370         }
371
372         return null;
373     }
374
375     private Node find(Node oldRoot, Node oldObject, Node newRoot) {
376         // Walk down the tree to locate oldObject, and in the process, pick the same child for newRoot
377
@SuppressWarnings JavaDoc("unchecked")
378         List JavaDoc<?extends Node> oldChildren = oldRoot.childNodes();
379         @SuppressWarnings JavaDoc("unchecked")
380         List JavaDoc<?extends Node> newChildren = newRoot.childNodes();
381         Iterator JavaDoc<?extends Node> itOld = oldChildren.iterator();
382         Iterator JavaDoc<?extends Node> itNew = newChildren.iterator();
383
384         while (itOld.hasNext()) {
385             if (!itNew.hasNext()) {
386                 return null; // No match - the trees have changed structure
387
}
388
389             Node o = itOld.next();
390             Node n = itNew.next();
391
392             if (o == oldObject) {
393                 // Found it!
394
return n;
395             }
396
397             // Recurse
398
Node match = find(o, oldObject, n);
399
400             if (match != null) {
401                 return match;
402             }
403         }
404
405         if (itNew.hasNext()) {
406             return null; // No match - the trees have changed structure
407
}
408
409         return null;
410     }
411
412     private class RubyElementHandle<T extends Element> extends ElementHandle<T> {
413         private Node root;
414         private T object;
415
416         private RubyElementHandle(Node root, T object) {
417             this.root = root;
418             this.object = object;
419         }
420
421         public boolean signatureEquals(ElementHandle handle) {
422             // XXX TODO
423
return false;
424         }
425
426         public FileObject getFileObject() {
427             if (object instanceof IndexedElement) {
428                 return ((IndexedElement)object).getFileObject();
429             }
430
431             return null;
432         }
433     }
434 }
435
Popular Tags