package org.mozilla.javascript;
import java.io.IOException;
/**
* Subclass of Rhino's Parser that saves information about the token stream
* and error message to allow more helpful error messages.
*
* @author David Greenspan for AppJet
*/
/*
This class is written with speed in mind, to some extent. Rhino's tokenizer
is pretty efficient, and we wouldn't want to slow it down by, for example,
creating a TokenInfo object on the heap for every token seen.
*/
/*APPJET*/
public class InformativeParser extends Parser {
public static class InformativeEvaluatorException extends EvaluatorException {
final ParseErrorInfo pei;
InformativeEvaluatorException(String errorMessage,
String sourceName, int lineNumber,
String lineSource, int columnNumber,
ParseErrorInfo peInfo) {
super(errorMessage, sourceName,
lineNumber, lineSource, columnNumber);
pei = peInfo;
}
public ParseErrorInfo getParseErrorInfo() {
return pei;
}
}
public static class ParseErrorInfo {
ParseErrorInfo() {}
String messageId = null;
String messageArg = null;
final int tokenMaxHistory = 10;
// ring buffers
final int[] tokenTypes = new int[tokenMaxHistory];
final String[] tokenStrings = new String[tokenMaxHistory];
final double[] tokenNumbers = new double[tokenMaxHistory];
final int[] tokenLineNumbers = new int[tokenMaxHistory];
final int[] tokenLineOffsets = new int[tokenMaxHistory];
int nextBufPos = 0;
int historyLength = 0;
boolean tokenPeeking = false;
int peekSlot;
void reportPeekToken(int type, String str, double num, int lineno,
int lineOffset) {
if (! tokenPeeking) {
peekSlot = nextBufPos;
tokenTypes[nextBufPos] = type;
tokenStrings[nextBufPos] = str;
tokenNumbers[nextBufPos] = num;
tokenLineNumbers[nextBufPos] = lineno;
tokenLineOffsets[nextBufPos] = lineOffset;
nextBufPos++;
if (nextBufPos == tokenMaxHistory) nextBufPos = 0;
if (historyLength < tokenMaxHistory) historyLength++;
tokenPeeking = true;
}
}
void reportConsumeToken() {
tokenPeeking = false;
}
private TokenInfo backToken(int n) {
// 0 is most recent token added to history
if (n >= historyLength) return null;
int i = (nextBufPos - 1 - n);
while (i < 0) i += tokenMaxHistory;
return new TokenInfo(tokenTypes[i], tokenStrings[i],
tokenNumbers[i], tokenLineNumbers[i],
tokenLineOffsets[i]);
}
public String getMessageId() { return messageId; }
public String getMessageArg() { return messageArg; }
public TokenInfo getPeekToken() {
if (tokenPeeking) return backToken(0);
return null;
}
public TokenInfo getPrevToken(int n) {
// 1 = last non-peek token seen, 2 = before that, etc.
if (! tokenPeeking) n--;
return backToken(n);
}
public TokenInfo getPrevToken() {
return getPrevToken(1);
}
}
public static class TokenInfo {
private int type, lineno, lineOffset;
private String str;
private double num;
TokenInfo(int type, String str, double num, int lineno,
int lineOffset) {
this.type = type; this.str = str; this.num = num;
this.lineno = lineno; this.lineOffset = lineOffset;
}
public int getType() { return type; }
public int getLineNumber() { return lineno; }
public int getLineOffset() { return lineOffset; }
public double getNumber() { return num; }
public String getString() { return str; }
}
ParseErrorInfo info = new ParseErrorInfo();
void doErrorReporterError(String message, String sourceURI, int line,
String lineText, int lineOffset) {
throw new InformativeEvaluatorException(message, sourceURI, line,
lineText, lineOffset, info);
}
public InformativeParser(CompilerEnvirons compilerEnv) {
// we override most calls to the parent's ErrorReporter anyway
super(compilerEnv, DefaultErrorReporter.instance);
}
@Override int peekToken() throws IOException {
int tt = super.peekToken();
info.reportPeekToken(tt, ts.getString(), ts.getNumber(),
ts.getLineno(), ts.getOffset());
return tt;
}
@Override void consumeToken() {
super.consumeToken();
info.reportConsumeToken();
}
@Override void addWarning(String messageId, String messageArg)
{
info.messageId = messageId;
info.messageArg = messageArg;
String message = ScriptRuntime.getMessage1(messageId, messageArg);
if (compilerEnv.reportWarningAsError()) {
++syntaxErrorCount;
doErrorReporterError(message, sourceURI, ts.getLineno(),
ts.getLine(), ts.getOffset());
}
else { /* don't report */ }
}
@Override void addError(String messageId)
{
info.messageId = messageId;
++syntaxErrorCount;
String message = ScriptRuntime.getMessage0(messageId);
doErrorReporterError(message, sourceURI, ts.getLineno(),
ts.getLine(), ts.getOffset());
}
@Override void addError(String messageId, String messageArg)
{
info.messageId = messageId;
info.messageArg = messageArg;
++syntaxErrorCount;
String message = ScriptRuntime.getMessage1(messageId, messageArg);
doErrorReporterError(message, sourceURI, ts.getLineno(),
ts.getLine(), ts.getOffset());
}
@Override protected Decompiler createDecompiler(CompilerEnvirons env) {
return new MyDecompiler();
}
public static final ErrorReporter THROW_INFORMATIVE_ERRORS
= new ErrorReporter() {
public void warning(String message, String sourceURI, int line,
String lineText, int lineOffset) {
DefaultErrorReporter.instance.warning
(message, sourceURI, line, lineText, lineOffset);
}
public void error(String message, String sourceURI, int line,
String lineText, int lineOffset) {
DefaultErrorReporter.instance.error
(message, sourceURI, line, lineText, lineOffset);
}
public EvaluatorException runtimeError(String message,
String sourceURI,
int line, String lineText,
int lineOffset) {
return DefaultErrorReporter.instance.runtimeError
(message, sourceURI, line, lineText, lineOffset);
}
};
public static Parser makeParser(CompilerEnvirons compilerEnv,
ErrorReporter errorReporter) {
if (errorReporter == THROW_INFORMATIVE_ERRORS) {
return new InformativeParser(compilerEnv);
}
else {
return new Parser(compilerEnv, errorReporter);
}
}
private class MyDecompiler extends Decompiler {
@Override void addRegexp(String regexp, String flags) {
super.addRegexp(regexp, flags);
String str = '/'+regexp+'/'+flags;
info.reportPeekToken(Token.REGEXP, str, ts.getNumber(),
ts.getLineno(), ts.getOffset());
info.reportConsumeToken();
}
}
}