/**
* 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.php.internal.ui.editor.scanner;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java_cup.runtime.Symbol;
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 com.aptana.core.logging.IdeLog;
import com.aptana.core.util.StringUtil;
import com.aptana.editor.common.CommonUtil;
import com.aptana.editor.php.PHPEditorPlugin;
import com.aptana.editor.php.core.PHPVersionProvider;
import com.aptana.editor.php.internal.parser.PHPTokenType;
import com.aptana.editor.php.internal.ui.editor.scanner.tokenMap.IPHPTokenMapper;
import com.aptana.editor.php.internal.ui.editor.scanner.tokenMap.PHPTokenMapperFactory;
/**
* Hook the php token scanner and the parser to tokenize
*
* @author Shalom Gibly <sgibly@aptana.com>
*/
public class PHPCodeScanner implements ITokenScanner
{
private final IPHPTokenScanner fScanner;
private Queue<QueuedToken> queue;
private int fLength;
private int fOffset;
private IDocument document;
private int originalDocumentOffset;
private IToken lastToken;
private boolean inFunctionDeclaration;
/**
* Constructs a new PHPCodeScanner
*/
public PHPCodeScanner()
{
fScanner = new PHPTokenScanner(PHPVersionProvider.getPHPVersion(null));
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.text.rules.ITokenScanner#getTokenLength()
*/
public int getTokenLength()
{
return fLength;
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.text.rules.ITokenScanner#getTokenOffset()
*/
public int getTokenOffset()
{
return fOffset;
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
*/
public IToken nextToken()
{
IToken token = pop();
if (token.isEOF())
{
return Token.EOF;
}
IPHPTokenMapper tokenMapper = PHPTokenMapperFactory.getMapper(fScanner.getPHPVersion());
token = tokenMapper.mapToken((Symbol) token.getData(), this);
if (scopeEquals(token, PHPTokenType.STORAGE_TYPE_FUNCTION))
{
inFunctionDeclaration = true;
}
// token scope is "default.php" and last was "storage.type.function.php", make this one
// "entity.name.function.php"
else if (scopeEquals(lastToken, PHPTokenType.STORAGE_TYPE_FUNCTION)
&& (scopeEquals(token, StringUtil.EMPTY) || scopeEquals(token, PHPTokenType.CONSTANT_OTHER)))
{
token = getToken(PHPTokenType.ENTITY_FUNCTION.toString());
}
// token scope is "default.php" and last was "storage.type.class.php", make this one
// "entity.name.type.class.php"
else if (scopeEquals(lastToken, PHPTokenType.STORAGE_TYPE_CLASS)
&& (scopeEquals(token, StringUtil.EMPTY) || scopeEquals(token, PHPTokenType.CONSTANT_OTHER)))
{
token = getToken(PHPTokenType.ENTITY_CLASS.toString());
}
// When we hit right paren, jump out of function declaration
else if (inFunctionDeclaration && scopeEquals(lastToken, PHPTokenType.PUNCTUATION_PARAM_RIGHT))
{
inFunctionDeclaration = false;
}
// Only use these special scopes for parens in function definitions
else if (!inFunctionDeclaration
&& (scopeEquals(token, PHPTokenType.PUNCTUATION_PARAM_LEFT) || scopeEquals(token,
PHPTokenType.PUNCTUATION_PARAM_RIGHT)))
{
token = getToken(StringUtil.EMPTY);
}
// number inside array access
else if (scopeEquals(lastToken, PHPTokenType.PUNCTUATION_LBRACKET)
&& scopeEquals(token, PHPTokenType.CONSTANT_NUMERIC))
{
token = getToken("variable.other.php constant.numeric.php"); //$NON-NLS-1$
}
// ->identifier
else if (scopeEquals(lastToken, PHPTokenType.KEYWORD_OP_CLASS)
&& (scopeEquals(token, StringUtil.EMPTY) || scopeEquals(token, PHPTokenType.CONSTANT_OTHER)))
{
// Lookahead to see if there's a trailing paren! If so, make it a function-call, otherwise it's a property
int lastOffset = getTokenOffset();
int lastLength = getTokenLength();
List<QueuedToken> popped = new ArrayList<QueuedToken>();
IToken next = pop();
while (next.isWhitespace())
{
popped.add(new QueuedToken(next, getTokenOffset(), getTokenLength()));
next = pop();
}
popped.add(new QueuedToken(next, getTokenOffset(), getTokenLength()));
if (!next.isEOF())
{
IToken nextMapped = tokenMapper.mapToken((Symbol) next.getData(), this);
if (scopeEquals(nextMapped, PHPTokenType.PUNCTUATION_PARAM_LEFT))
{
token = getToken(PHPTokenType.META_FUNCTION_CALL_OBJECT);
}
else
{
token = getToken(PHPTokenType.VARIABLE_OTHER_PROPERTY);
}
}
else
{
token = getToken(PHPTokenType.VARIABLE_OTHER_PROPERTY);
}
// Push the tokens we looked ahead back onto our queue
for (QueuedToken addBack : popped)
{
push(addBack.getToken(), addBack.getOffset(), addBack.getLength());
}
fLength = lastLength;
fOffset = lastOffset;
}
// function calls
else if (scopeEquals(token, PHPTokenType.CONSTANT_OTHER))
{
// Lookahead to see if there's a trailing paren! If so, make it a function-call
int lastOffset = getTokenOffset();
int lastLength = getTokenLength();
List<QueuedToken> popped = new ArrayList<QueuedToken>();
IToken next = pop();
while (next.isWhitespace())
{
popped.add(new QueuedToken(next, getTokenOffset(), getTokenLength()));
next = pop();
}
popped.add(new QueuedToken(next, getTokenOffset(), getTokenLength()));
if (!next.isEOF())
{
IToken nextMapped = tokenMapper.mapToken((Symbol) next.getData(), this);
if (scopeEquals(nextMapped, PHPTokenType.PUNCTUATION_PARAM_LEFT))
{
token = getToken(PHPTokenType.META_FUNCTION_CALL);
}
}
// Push the tokens we looked ahead back onto our queue
for (QueuedToken addBack : popped)
{
push(addBack.getToken(), addBack.getOffset(), addBack.getLength());
}
fLength = lastLength;
fOffset = lastOffset;
}
if (token.isOther())
{
lastToken = token;
}
if (inFunctionDeclaration)
{
return getToken("meta.function.php " + token.getData()); //$NON-NLS-1$
}
return token;
}
private boolean scopeEquals(IToken token, PHPTokenType type)
{
return scopeEquals(token, type.toString());
}
private boolean scopeEquals(IToken token, String scope)
{
return token != null && token.getData() != null && scope.equals(token.getData());
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.text.rules.ITokenScanner#setRange(org.eclipse.jface.text.IDocument, int, int)
*/
public void setRange(IDocument document, int offset, int length)
{
this.document = document;
this.originalDocumentOffset = offset;
fScanner.setRange(document, offset, length);
reset();
}
/**
* Returns the string value in the document that was represented by the given symbol.<br>
* The extraction of the value is done from the document range that was set in the
* {@link #setRange(IDocument, int, int)} call.
*
* @param sym
* @return A String value extracted from the document
*/
public String getSymbolValue(Symbol sym)
{
try
{
// Note: don't check for null contents because this should only be used
// from the scanner while the contents are still available (so, this
// situation would really be an error).
String contents = fScanner.getContents();
return contents.substring(sym.left, sym.right);
}
catch (Exception e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "PHP code-scanner - Error getting a symbol value", e); //$NON-NLS-1$
}
return null;
}
public IToken getToken(PHPTokenType type)
{
return getToken(type.toString());
}
public IToken getToken(String tokenName)
{
return CommonUtil.getToken(tokenName);
}
public 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 IToken pop()
{
IToken token = null;
if (queue == null || queue.isEmpty())
{
token = fScanner.nextToken();
fOffset = fScanner.getTokenOffset();
fLength = fScanner.getTokenLength();
}
else
{
QueuedToken queued = queue.poll();
fOffset = queued.getOffset();
fLength = queued.getLength();
token = queued.getToken();
}
if (token == null || token.isEOF())
{
return Token.EOF;
}
return token;
}
private void push(IToken next)
{
push(next, getTokenOffset(), getTokenLength());
}
private void push(IToken next, int offset, int length)
{
if (queue == null)
{
queue = new LinkedList<QueuedToken>();
}
queue.add(new QueuedToken(next, offset, length));
}
private void reset()
{
queue = null;
inFunctionDeclaration = false;
lastToken = null;
}
}