/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions). * Please see the license.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.ruby; import java.util.List; import java.util.Vector; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.rules.IToken; import org.eclipse.jface.text.rules.ITokenScanner; import org.eclipse.jface.text.rules.Token; import org.jrubyparser.parser.Tokens; import com.aptana.core.util.StringUtil; public class RubyCodeScanner implements ITokenScanner { private RubyTokenScanner fScanner; private boolean nextIsMethodName; private boolean nextIsModuleName; private boolean nextIsClassName; private boolean inPipe; private boolean lookForBlock; private boolean nextAreArgs; private List<QueuedToken> queue; private int fLength; private int fOffset; private int fOrigOffset; public RubyCodeScanner() { fScanner = new RubyTokenScanner(); } public int getTokenLength() { return fLength; } public int getTokenOffset() { return fOffset; } public IToken nextToken() { IToken intToken = pop(); if (intToken.isEOF()) { return Token.EOF; } Integer data = (Integer) intToken.getData(); if (lookForBlock) { if (!inPipe && data.intValue() != Tokens.tPIPE && data.intValue() != Tokens.tWHITESPACE) { lookForBlock = false; } } if (nextAreArgs && (isNewline(data) || data.intValue() == RubyTokenScanner.SEMICOLON)) { nextAreArgs = false; } // Convert the integer tokens into tokens containing color information! if (isKeyword(data.intValue())) { switch (data.intValue()) { case Tokens.k__FILE__: case Tokens.k__LINE__: case Tokens.kSELF: if (nextIsClassName) { nextIsClassName = false; return getToken(IRubyScopeConstants.CLASS_NAME); } return getToken(IRubyScopeConstants.LANGUAGE_VARIABLE); case Tokens.kNIL: case Tokens.kTRUE: case Tokens.kFALSE: return getToken(IRubyScopeConstants.LANGUAGE_CONSTANT); case Tokens.kAND: case Tokens.kNOT: case Tokens.kOR: return getToken(IRubyScopeConstants.OPERATOR_KEYWORD); case Tokens.kDO_BLOCK: case Tokens.kDO: lookForBlock = true; return getToken(IRubyScopeConstants.DO_KEYWORD); case Tokens.kCLASS: nextAreArgs = false; nextIsClassName = true; return getToken(IRubyScopeConstants.CLASS_KEYWORD); case Tokens.kMODULE: nextAreArgs = false; nextIsModuleName = true; return getToken(IRubyScopeConstants.MODULE_KEYWORD); case Tokens.kDEF: nextAreArgs = false; nextIsMethodName = true; return getToken(IRubyScopeConstants.DEF_KEYWORD); default: if (nextIsMethodName) { nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); } return getToken(IRubyScopeConstants.CONTROL_KEYWORD); } } switch (data.intValue()) { case RubyTokenScanner.ASSIGNMENT: return getToken(IRubyScopeConstants.OPERATOR_ASSIGNMENT); case Tokens.tCMP: /* <=> */ case Tokens.tMATCH: /* =~ */ case Tokens.tNMATCH: /* !~ */ case Tokens.tEQ: /* == */ case Tokens.tEQQ: /* === */ case Tokens.tNEQ: /* != */ case Tokens.tGEQ: /* >= */ case Tokens.tLEQ: case Tokens.tLT: case Tokens.tGT: if (nextIsMethodName) { nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); } return getToken(IRubyScopeConstants.OPERATOR_COMPARISON); case Tokens.tSTAR: if (nextAreArgs) // could be un-named rest arg { return getToken(IRubyScopeConstants.FUNCTION_PARAMETER); } // intentionally fall-through case Tokens.tAMPER: // $codepro.audit.disable nonTerminatedCaseClause case Tokens.tPERCENT: case Tokens.tPOW: case Tokens.tSTAR2: case Tokens.tPLUS: case Tokens.tMINUS: case Tokens.tDIVIDE: if (nextIsMethodName) { nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); } return getToken(IRubyScopeConstants.OPERATOR_ARITHMETIC); case Tokens.tANDOP: case Tokens.tAMPER2: // & case Tokens.tTILDE: case Tokens.tBANG: case Tokens.tOROP: case Tokens.tCARET: case RubyTokenScanner.QUESTION: if (nextIsMethodName) { nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); } return getToken(IRubyScopeConstants.OPERATOR_LOGICAL); case Tokens.tAREF: case Tokens.tASET: case Tokens.tUPLUS: case Tokens.tUMINUS: case Tokens.tUMINUS_NUM: nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); case Tokens.tPIPE: if (lookForBlock) { inPipe = !inPipe; if (!inPipe) { lookForBlock = false; } return getToken(IRubyScopeConstants.VARIABLE_SEPARATOR); } if (nextIsMethodName) { nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); } return getToken(IRubyScopeConstants.OPERATOR_LOGICAL); case Tokens.tLPAREN: case Tokens.tLPAREN2: if (nextAreArgs) { return getToken(IRubyScopeConstants.FUNCTION_DEF_PAREN); } return getToken(IRubyScopeConstants.PAREN); case Tokens.tLBRACE: lookForBlock = true; return getToken(IRubyScopeConstants.SCOPE_PUNCTUATION); case Tokens.tLBRACK: case Tokens.tRBRACK: return getToken(IRubyScopeConstants.ARRAY_PUNCTUATION); case Tokens.tLCURLY: case Tokens.tRCURLY: return getToken(IRubyScopeConstants.SCOPE_PUNCTUATION); case RubyTokenScanner.COMMA: return getToken(IRubyScopeConstants.COMMA); case Tokens.tRPAREN: if (nextAreArgs) { nextAreArgs = false; return getToken(IRubyScopeConstants.FUNCTION_DEF_PAREN); } return getToken(IRubyScopeConstants.PAREN); case Tokens.tLSHFT: if (nextIsClassName) { return getToken(IRubyScopeConstants.CLASS_NAME); } if (nextIsMethodName) { nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); } return getToken(IRubyScopeConstants.AUGMENTED_ASSIGNMENT); case Tokens.tOP_ASGN: return getToken(IRubyScopeConstants.AUGMENTED_ASSIGNMENT); case Tokens.tASSOC: return getToken(IRubyScopeConstants.HASH_SEPARATOR); case RubyTokenScanner.CHARACTER: return getToken(IRubyScopeConstants.CHARACTER); case Tokens.tCOLON2: case Tokens.tCOLON3: return getToken(IRubyScopeConstants.INHERITANCE_PUNCTUATION); case Tokens.tFLOAT: case Tokens.tINTEGER: return getToken(IRubyScopeConstants.NUMERIC); case Tokens.tSYMBEG: return getToken(IRubyScopeConstants.SYMBOL); case Tokens.tGVAR: return getToken(IRubyScopeConstants.GLOBAL_VARIABLE); case Tokens.tIVAR: return getToken(IRubyScopeConstants.INSTANCE_VARIABLE); case Tokens.tCVAR: return getToken(IRubyScopeConstants.CLASS_VARIABLE); case Tokens.tCONSTANT: if (nextIsModuleName) { nextIsModuleName = false; return getToken(IRubyScopeConstants.MODULE_NAME); } if (nextIsClassName) { nextIsClassName = false; return getToken(IRubyScopeConstants.CLASS_NAME); } int nextToken = peek(); if (nextToken == Tokens.tCOLON2 || nextToken == Tokens.tDOT) { return getToken(IRubyScopeConstants.SUPPORT_CLASS); } return getToken(IRubyScopeConstants.CONSTANT_OTHER); case Tokens.yyErrorCode: return getToken(IRubyScopeConstants.ERROR); case Tokens.tWHITESPACE: return Token.WHITESPACE; case Tokens.tDOT: return getToken(IRubyScopeConstants.SEPARATOR_METHOD); case Tokens.tIDENTIFIER: case Tokens.tFID: if (nextIsMethodName) { nextIsMethodName = false; nextAreArgs = true; return getToken(IRubyScopeConstants.FUNCTION_NAME); } if (nextAreArgs) { return getToken(IRubyScopeConstants.FUNCTION_PARAMETER); } if (lookForBlock && inPipe) { return getToken(IRubyScopeConstants.BLOCK_VARIABLE); } if ("new".equals(getSourceForCurrentToken())) //$NON-NLS-1$ { return getToken(IRubyScopeConstants.SPECIAL_METHOD); } // intentionally fall through default: // $codepro.audit.disable nonTerminatedCaseClause return getToken(StringUtil.EMPTY); } } @SuppressWarnings("nls") protected boolean isNewline(Integer data) { if (data.intValue() == RubyTokenScanner.NEWLINE) { return true; } if (data.intValue() != Tokens.tWHITESPACE) { return false; } // make sure it's actually a newline String tokenSrc = getSourceForCurrentToken(); if (tokenSrc == null) { return false; } return tokenSrc.equals("\r\n") || tokenSrc.equals("\n") || tokenSrc.equals("\r"); // $codepro.audit.disable // platformSpecificLineSeparator } private String getSourceForCurrentToken() { return fScanner.getSource(fOffset - fOrigOffset, fLength); } protected IToken getToken(String tokenName) { return new Token(tokenName); } private IToken pop() { IToken intToken = null; if (queue == null || queue.isEmpty()) { intToken = fScanner.nextToken(); fOffset = fScanner.getTokenOffset(); fLength = fScanner.getTokenLength(); } else { QueuedToken queued = queue.remove(0); fOffset = queued.getOffset(); fLength = queued.getLength(); intToken = queued.getToken(); } if (intToken == null) return Token.EOF; Integer data = (Integer) intToken.getData(); if (data == null) return Token.EOF; return intToken; } private int peek() { int oldOffset = getTokenOffset(); int oldLength = getTokenLength(); IToken next = pop(); push(next); fOffset = oldOffset; fLength = oldLength; if (next.isEOF()) { return -1; } Integer data = (Integer) next.getData(); return data.intValue(); } private void push(IToken next) { if (queue == null) { queue = new Vector<QueuedToken>(); } queue.add(new QueuedToken(next, getTokenOffset(), getTokenLength())); } public void setRange(IDocument document, int offset, int length) { fScanner.setRange(document, offset, length); reset(); fOrigOffset = offset; } private void reset() { nextIsMethodName = false; nextIsModuleName = false; nextIsClassName = false; inPipe = false; lookForBlock = false; nextAreArgs = false; queue = null; } private boolean isKeyword(int i) { return i >= RubyTokenScanner.MIN_KEYWORD && i <= RubyTokenScanner.MAX_KEYWORD; } }