package edu.ucsd.arcum.interpreter.parser;
import static edu.ucsd.arcum.exceptions.ArcumError.fatalUserError;
import static edu.ucsd.arcum.interpreter.parser.TokenData.tokenToString;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameEOF;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameERROR;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameIdentifier;
import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.TokenNameLBRACKET;
import java.util.Set;
import org.eclipse.core.resources.IResource;
import org.eclipse.jdt.core.compiler.InvalidInputException;
import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
import org.eclipse.jdt.internal.compiler.parser.Scanner;
import com.google.common.collect.Sets;
import edu.ucsd.arcum.exceptions.SourceLocation;
@SuppressWarnings("restriction")
public class BacktrackingScanner
{
public interface TokenCountListener
{
void count();
}
public static final int TokenNameARCUMVARIABLE = 2001;
public static final int TokenNameARCUMBEGINQUOTE = 2002;
private char[] buff;
private IResource resource;
private Scanner scanner;
private int lookahead;
private final Set<TokenCountListener> tokenCountListeners;
public BacktrackingScanner(String contents) {
this.buff = contents.toCharArray();
this.tokenCountListeners = Sets.newHashSet();
backtrack();
}
public BacktrackingScanner(String source, IResource resource) {
this(source);
this.resource = resource;
}
// revert the scanner to read from the start
public void backtrack() {
this.scanner = new Scanner(false/*tokenizeComments*/,
false/*tokenizeWhiteSpace*/, false/*checkNSL*/,
ClassFileConstants.JDK1_5/*sourceLevel*/, null/*taskTags*/,
null/*taskPriorities*/, false/*isTaskCaseSensitive*/);
scanner.setSource(buff);
nextToken();
}
public String getCurrentTokenString() {
return scanner.getCurrentTokenString();
}
// returns TokenNameARCUMVARIABLE in the event of a backtick variable
// reference. The call to getCurrentTokenString can be used to get the
// Arcum variable's name (minus the leading "`" tick).
public int nextToken() {
int token = _nextToken();
if (token == TokenNameERROR) {
String lexeme = scanner.getCurrentTokenString();
if (!lexeme.equals("`")) {
fatalUserError(getCurrentLocation(), "Unknown input character: %s",
lexeme);
}
token = _nextToken();
if (token == TokenNameLBRACKET) {
token = TokenNameARCUMBEGINQUOTE;
}
else if (token == TokenNameIdentifier) {
token = TokenNameARCUMVARIABLE;
}
else {
fatalUserError(getCurrentLocation(),
"No variable name specified after the \"`\"");
}
}
this.lookahead = token;
for (TokenCountListener tokenCountListener : tokenCountListeners) {
tokenCountListener.count();
}
return token;
}
private int _nextToken() {
try {
return scanner.getNextToken();
}
catch (InvalidInputException e) {
e.printStackTrace();
System.err.printf("nextToken error%n");
return TokenNameEOF;
}
}
public int lookahead() {
return lookahead;
}
public void matchIfPresent(int token) {
if (lookahead == token) {
match();
}
}
public void match(int token) {
if (lookahead != token) {
fatalUserError(getCurrentLocation(), "Parsing error: Expected a %s%n",
tokenToString(token));
}
match();
}
public void match() {
nextToken();
}
public boolean lookaheadIsNoneOf(int... tokens) {
for (int token : tokens) {
if (lookahead == token) {
return false;
}
}
return true;
}
public boolean lookaheadEquals(int token) {
return lookahead == token;
}
public int getCurrentTokenStartPosition() {
return scanner.getCurrentTokenStartPosition();
}
public int getCurrentTokenEndPosition() {
return scanner.getCurrentTokenEndPosition();
}
public String getCurrentStringLiteral() {
return scanner.getCurrentStringLiteral();
}
public int getStartPosition() {
// NOTE: in the case of `vars (TokenNameARCUMVARIABLE) this start
// position won't be accurate -- this is currently worked around in
// the parser, but would be cleaner if done here
return scanner.startPosition;
}
public String getText(int startPositition, int length) {
StringBuilder buff = new StringBuilder();
buff.append(scanner.source, startPositition, length);
return buff.toString();
}
// Eat tokens until we see our first 'token' or EOF; Returns what was
// actually seen
public int consumeUntil(int toFind) {
int token;
do {
token = nextToken();
} while (token != toFind && token != TokenNameEOF);
return token;
}
public int getLineNumber(int position) {
return scanner.getLineNumber(position);
}
public SourceLocation getCurrentLocation() {
if (resource != null) {
SourceLocation result = new SourceLocation(resource, scanner
.getCurrentTokenStartPosition(),
scanner.getCurrentTokenEndPosition() + 1, scanner
.getLineNumber(scanner.currentPosition));
return result;
}
return null;
}
@Override public String toString() {
return scanner.toString();
}
public boolean containsToken(int... tokens) {
Set<Integer> lookup = Sets.newHashSet();
for (int token : tokens) {
lookup.add(token);
}
for (;;) {
if (lookahead == TokenNameEOF)
break;
else if (lookup.contains(lookahead)) {
return true;
}
match();
}
return false;
}
public void addTokenCountListener(TokenCountListener tokenCountListener) {
tokenCountListeners.add(tokenCountListener);
}
public void removeTokenCountListener(TokenCountListener tokenCountListener) {
tokenCountListeners.remove(tokenCountListener);
}
}