/** * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package org.python.pydev.parser.fastparser; import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.python.pydev.core.docutils.ParsingUtils; import org.python.pydev.core.docutils.PySelection; import org.python.pydev.core.docutils.SyntaxErrorException; import org.python.pydev.core.docutils.TabNannyDocIterator; import org.python.pydev.core.log.Log; import org.python.pydev.shared_core.parsing.IScopesParser; import org.python.pydev.shared_core.parsing.Scopes; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.structure.Tuple3; /** * This parser is a bit different from the others, as its output is not an AST, but a structure defining the scopes * in a document (used for doing the scope selection action). * * @author fabioz */ public class ScopesParser implements IScopesParser { @Override public Scopes createScopes(IDocument doc) { this.scopes = new Scopes(); this.doc = doc; this.lineOffsetToIndent = new TreeMap<Integer, Integer>(); try { TabNannyDocIterator nannyDocIterator = new TabNannyDocIterator(doc, true, false); while (nannyDocIterator.hasNext()) { Tuple3<String, Integer, Boolean> next = nannyDocIterator.next(); this.lineOffsetToIndent.put(next.o2, next.o1.length()); } } catch (BadLocationException e1) { throw new RuntimeException(e1); } try { return this.createScopes(); } catch (SyntaxErrorException e) { throw new RuntimeException(e); } } private Scopes scopes; private SortedMap<Integer, Integer> lineOffsetToIndent; private IDocument doc; public ScopesParser() { } private Scopes createScopes() throws SyntaxErrorException { int globalScope = this.scopes.startScope(0, Scopes.TYPE_MODULE); int offset = createInternalScopes(ParsingUtils.create(doc, true), 0); this.scopes.endScope(globalScope, offset, Scopes.TYPE_MODULE); return this.scopes; } private int createInternalScopes(ParsingUtils parsingUtils, int offsetDelta) { int docLen = parsingUtils.len(); int offset = 0; FastStringBuffer lineMemo = new FastStringBuffer(); int memoStart = 0; int id; for (; offset < docLen; offset++) { char ch = parsingUtils.charAt(offset); switch (ch) { case '#': id = this.scopes.startScope(offsetDelta + offset, Scopes.TYPE_COMMENT); offset = parsingUtils.eatComments(null, offset); this.scopes.endScope(id, offsetDelta + offset, Scopes.TYPE_COMMENT); break; case '{': case '[': case '(': int baseOffset = offset; try { offset = parsingUtils.eatPar(offset, null, ch); //If a SyntaxError is raised here, we won't create a scope! id = this.scopes.startScope(offsetDelta + baseOffset + 1, Scopes.TYPE_PEER); try { String cs = doc.get(offsetDelta + baseOffset + 1, offset - baseOffset - 1); createInternalScopes(ParsingUtils.create(cs, true), offsetDelta + baseOffset + 1); } catch (BadLocationException e1) { Log.log(e1); } this.scopes.endScope(id, offsetDelta + offset, Scopes.TYPE_PEER); } catch (SyntaxErrorException e2) { } break; case '\'': //Fallthrough case '\"': baseOffset = offset; try { offset = parsingUtils.eatLiterals(null, offset); //If a SyntaxError is raised here, we won't create a scope! id = this.scopes.startScope(offsetDelta + baseOffset, Scopes.TYPE_STRING); this.scopes.endScope(id, offsetDelta + offset + 1, Scopes.TYPE_STRING); } catch (SyntaxErrorException e1) { } break; case ':': if (PySelection.startsWithIndentToken(lineMemo.toString().trim())) { SortedMap<Integer, Integer> subMap = lineOffsetToIndent.tailMap(offsetDelta + memoStart + 1); Integer level = lineOffsetToIndent.get(offsetDelta + memoStart); if (level == null) { //It's a ':' inside a parens continue; } Set<Entry<Integer, Integer>> entrySet = subMap.entrySet(); boolean found = false; id = this.scopes.startScope(memoStart + level, Scopes.TYPE_SUITE); int id2 = -1; for (int j = offset + 1; j < docLen; j++) { char c = parsingUtils.charAt(j); if (Character.isWhitespace(c)) { continue; } if (c == '#') { j = parsingUtils.eatComments(null, j); continue; } id2 = this.scopes.startScope(offsetDelta + j, Scopes.TYPE_SUITE); break; } for (Entry<Integer, Integer> entry : entrySet) { if (level >= entry.getValue()) { found = true; Integer scopeEndOffset = entry.getKey(); try { int line = doc.getLineOfOffset(scopeEndOffset); if (line > 0) { //We want it to end at the end of the previous line (not at the start of the next scope) IRegion lineInformation = doc.getLineInformation(line - 1); scopeEndOffset = lineInformation.getOffset() + lineInformation.getLength(); } } catch (BadLocationException e) { Log.log(e); } this.scopes.endScope(id, scopeEndOffset, Scopes.TYPE_SUITE); if (id2 > 0) { this.scopes.endScope(id2, scopeEndOffset, Scopes.TYPE_SUITE); } break; } } if (!found) { //Ends at the end of the document! this.scopes.endScope(id, offsetDelta + parsingUtils.len(), Scopes.TYPE_SUITE); if (id2 > 0) { this.scopes.endScope(id2, offsetDelta + parsingUtils.len(), Scopes.TYPE_SUITE); } } } break; case '\r': //Fallthrough case '\n': //Note that we don't add the \r nor \n to the memo (but we clear it if the line did not end with a \). if (lineMemo.length() > 0 && lineMemo.lastChar() != '\\') { lineMemo.clear(); } break; default: if (lineMemo.length() == 0) { memoStart = offset; } lineMemo.append(ch); break; } } return offset; } }