blob: 5870456c2ef894722032fa744baa8540c2f32884 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 xored software, Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* xored software, Inc. - initial API and Implementation (Vladimir Belov)
*******************************************************************************/
package org.eclipse.dltk.javascript.parser;
import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;
import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.BitSet;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.IntStream;
import org.antlr.runtime.MismatchedSetException;
import org.antlr.runtime.MismatchedTokenException;
import org.antlr.runtime.NoViableAltException;
import org.antlr.runtime.Parser;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenStream;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.ast.parser.ISourceParser;
import org.eclipse.dltk.compiler.env.IModuleSource;
import org.eclipse.dltk.compiler.problem.IProblemReporter;
import org.eclipse.dltk.compiler.problem.ProblemSeverity;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.SourceRange;
import org.eclipse.dltk.core.builder.ISourceLineTracker;
import org.eclipse.dltk.javascript.ast.Expression;
import org.eclipse.dltk.javascript.ast.Script;
import org.eclipse.dltk.javascript.internal.parser.JSCommonTokenStream;
import org.eclipse.dltk.javascript.internal.parser.NodeTransformerManager;
import org.eclipse.dltk.javascript.parser.JSParser.program_return;
import org.eclipse.dltk.javascript.parser.JSParser.standaloneExpression_return;
import org.eclipse.dltk.utils.TextUtils;
public class JavaScriptParser implements ISourceParser {
private boolean xmlEnabled = true;
public boolean isXmlEnabled() {
return xmlEnabled;
}
public void setXmlEnabled(boolean xmlEnabled) {
this.xmlEnabled = xmlEnabled;
}
public static final String PARSER_ID = "org.eclipse.dltk.javascript.NewParser";
static class JSBaseParser extends Parser {
Reporter reporter;
boolean xmlEnabled;
public JSBaseParser(TokenStream input) {
super(input);
}
protected boolean isXmlEnabled() {
return xmlEnabled;
}
protected void reportFailure(Throwable t) {
if (reporter != null && !peekState().hasErrors()) {
reporter.reportProblem(new JSProblem(t));
}
}
private JSParserMessages messages = null;
private JSParserMessages getMessages() {
if (messages == null) {
messages = new JSParserMessages();
}
return messages;
}
private String getTokenName(int token) {
String message = getMessages().get(token);
if (message == null) {
message = getTokenNames()[token];
}
return message;
}
@Override
public String getTokenErrorDisplay(Token t) {
final String message = getMessages().get(t.getType());
if (message != null) {
return message;
}
return super.getTokenErrorDisplay(t);
}
@Override
public void displayRecognitionError(String[] tokenNames,
RecognitionException re) {
peekState().incrementErrorCount();
if (reporter == null)
return;
String message;
ISourceRange range;
if (re instanceof NoViableAltException) {
range = convert(re.token);
final Token token = getLastToken(re.token);
message = getMessages().get(peekState().rule, token.getType());
if (message == null) {
message = "Unexpected " + getTokenErrorDisplay(re.token);
}
} else if (re instanceof MismatchedTokenException) {
MismatchedTokenException mte = (MismatchedTokenException) re;
if (re.token == Token.EOF_TOKEN) {
message = getTokenName(mte.expecting) + " expected";
} else {
message = "Mismatched input "
+ getTokenErrorDisplay(re.token);
if (mte.expecting >= 0 && mte.expecting < tokenNames.length) {
message += ", " + getTokenName(mte.expecting)
+ " expected";
}
}
range = convert(re.token);
if (range.getLength() + range.getOffset() >= inputLength()) {
int stop = inputLength() - 1;
int start = Math.min(stop - 1, range.getOffset() - 2);
range = new SourceRange(start, stop - start);
}
} else if (re instanceof MismatchedSetException) {
MismatchedSetException mse = (MismatchedSetException) re;
message = "Mismatched input " + getTokenErrorDisplay(re.token);
if (mse.expecting != null) {
message += " expecting set " + mse.expecting;
}
range = convert(re.token);
} else {
message = "Syntax Error:" + re.getMessage();
range = convert(re.token);
// stop = start + 1;}
}
reporter.setMessage(JavaScriptParserProblems.SYNTAX_ERROR, message);
reporter.setSeverity(ProblemSeverity.ERROR);
if (range != null) {
reporter.setRange(range.getOffset(),
range.getOffset() + range.getLength());
}
reporter.setLine(re.line - 1);
reporter.report();
}
private Token getLastToken(Token token) {
if (token == Token.EOF_TOKEN) {
final TokenStream stream = getTokenStream();
int index = stream.index();
while (index > 0) {
--index;
final Token prevToken = stream.get(index);
if (prevToken.getType() != JSParser.WhiteSpace
&& prevToken.getType() != JSParser.EOL) {
token = prevToken;
break;
}
}
}
return token;
}
private ISourceRange convert(Token token) {
token = getLastToken(token);
if (token == Token.EOF_TOKEN) {
return null;
}
return reporter.toSourceRange(token);
}
private int inputLength() {
return reporter.getLength();
}
/*
* Standard implementation contains forgotten debug System.err.println()
* and we don't need it at all.
*/
@Override
public void recoverFromMismatchedToken(IntStream input,
RecognitionException e, int ttype, BitSet follow)
throws RecognitionException {
// if next token is what we are looking for then "delete" this token
if (input.LA(2) == ttype) {
reportError(e);
beginResync();
input.consume(); // simply delete extra token
endResync();
input.consume(); // move past ttype token as if all were ok
return;
}
// insert "}" if expected
if (ttype == JSParser.RBRACE) {
displayRecognitionError(getTokenNames(), e);
return;
}
if (!recoverFromMismatchedElement(input, e, follow)) {
throw e;
}
}
protected void syncToSet() {
final BitSet follow = following[_fsp];
int mark = input.mark();
try {
Token first = null;
Token last = null;
while (!follow.member(input.LA(1))) {
if (input.LA(1) == Token.EOF) {
input.rewind();
mark = -1;
return;
}
last = input.LT(1);
if (first == null) {
first = last;
}
input.consume();
}
if (first != null && reporter != null) {
final ISourceRange end = convert(last);
reporter.setMessage(JavaScriptParserProblems.SYNTAX_ERROR,
"Unexpected input was discarded");
reporter.setSeverity(ProblemSeverity.ERROR);
reporter.setRange(convert(first).getOffset(),
end.getOffset() + end.getLength());
reporter.setLine(first.getLine() - 1);
reporter.report();
}
} finally {
if (mark != -1) {
input.release(mark);
}
}
}
protected void reportReservedKeyword(Token token) {
if (reporter == null)
return;
final ISourceRange range = convert(token);
reporter.setFormattedMessage(
JavaScriptParserProblems.RESERVED_KEYWORD, token.getText());
reporter.setSeverity(ProblemSeverity.ERROR);
reporter.setRange(range.getOffset(),
range.getOffset() + range.getLength());
reporter.setLine(token.getLine() - 1);
reporter.report();
}
protected void reportError(String message, Token token) {
if (reporter == null)
return;
final ISourceRange range = convert(token);
reporter.setMessage(JavaScriptParserProblems.SYNTAX_ERROR, message);
reporter.setSeverity(ProblemSeverity.ERROR);
reporter.setRange(range.getOffset(),
range.getOffset() + range.getLength());
reporter.setLine(token.getLine() - 1);
reporter.report();
}
/**
* Overrides the function to prevent NPE.
*
* The only change is <code>localFollowSet != null</code> check
*
* @see org.eclipse.dltk.javascript.parser.tests.Bug20110503#testCombinedFollowsNPE()
*/
@Override
protected BitSet combineFollows(boolean exact) {
int top = _fsp;
BitSet followSet = new BitSet();
for (int i = top; i >= 0; i--) {
BitSet localFollowSet = following[i];
followSet.orInPlace(localFollowSet);
if (exact && localFollowSet != null
&& !localFollowSet.member(Token.EOR_TOKEN_TYPE)) {
break;
}
}
followSet.remove(Token.EOR_TOKEN_TYPE);
return followSet;
}
protected void reportRuleError(RecognitionException re) {
reportError(re);
recover(input, re);
}
private final Stack<JSParserState> states = new Stack<JSParserState>();
protected void pushState(JSParserRule rule) {
states.push(new JSParserState(peekState(), rule));
}
protected void popState() {
states.pop();
}
public JSParserState peekState() {
return states.isEmpty() ? null : states.peek();
}
}
/**
* @since 2.0
*/
public Script parse(IModuleSource input, IProblemReporter reporter) {
Assert.isNotNull(input);
char[] source = input.getContentsAsCharArray();
return parse(
input.getModelElement(),
createTokenStream(source),
reporter == null ? null : new Reporter(TextUtils
.createLineTracker(source), reporter));
}
/**
* @since 2.0
*/
public Script parse(String source, IProblemReporter reporter) {
Assert.isNotNull(source);
return parse(null, createTokenStream(source),
TextUtils.createLineTracker(source), reporter);
}
/**
* Parse the specified string as JavaScript expression. Returns the
* expression node or <code>null</code> on unrecoverable errors.
* {@link org.eclipse.dltk.javascript.ast.ErrorExpression} could be also
* returned.
*
* @param source
* @param _reporter
* @return
*/
public Expression expression(String source, IProblemReporter _reporter) {
try {
final Reporter reporter = new Reporter(
TextUtils.createLineTracker(source), _reporter);
final JSTokenStream stream = createTokenStream(source);
stream.setReporter(reporter);
final JSParser parser = createTreeParser(stream, reporter);
final standaloneExpression_return root = parser
.standaloneExpression();
final JSTransformer transformer = new JSTransformer(
stream.getTokens(), parser.peekState().hasErrors());
transformer.setReporter(reporter);
return (Expression) transformer.transform(root);
} catch (Exception e) {
if (DLTKCore.DEBUG)
e.printStackTrace();
if (_reporter != null) {
_reporter.reportProblem(new JSProblem(e));
}
return null;
}
}
public JSParser createTreeParser(final JSTokenStream stream,
final Reporter reporter) {
final JSParser parser = new JSParser(stream);
parser.reporter = reporter;
parser.xmlEnabled = xmlEnabled;
return parser;
}
protected Script parse(IModelElement element, JSTokenStream stream,
ISourceLineTracker lineTracker, IProblemReporter reporter) {
return parse(element, stream, new Reporter(lineTracker, reporter));
}
protected Script parse(IModelElement element, JSTokenStream stream,
Reporter reporter) {
try {
stream.setReporter(reporter);
JSParser parser = createTreeParser(stream, reporter);
final program_return root = parser.program();
final NodeTransformer[] transformers = NodeTransformerManager
.createTransformers(element, reporter);
JSTransformer transformer = new JSTransformer(transformers,
stream.getTokens(), parser.peekState().hasErrors());
transformer.setReporter(reporter);
final Script script = transformer.transformScript(root);
if (element != null && element instanceof ISourceModule) {
script.setAttribute(JavaScriptParserUtil.ATTR_MODULE, element);
}
return script;
} catch (Exception e) {
JavaScriptParserPlugin.error(e);
if (reporter != null) {
reporter.reportProblem(new JSProblem(e));
}
// create empty output
return new Script();
}
}
public JSTokenStream createTokenStream(char[] source) {
CharStream charStream = new ANTLRStringStream(source, source.length);
return createTokenStream(charStream);
}
public JSTokenStream createTokenStream(String source) {
CharStream charStream = new ANTLRStringStream(source);
return createTokenStream(charStream);
}
/**
* @param input
* @param encoding
* @return
* @throws IOException
*/
public JSTokenStream createTokenStream(InputStream input, String encoding)
throws IOException {
CharStream charStream = new ANTLRInputStream(input, encoding);
return createTokenStream(charStream);
}
private JSTokenStream createTokenStream(CharStream charStream) {
if (xmlEnabled) {
return new DynamicTokenStream(new JavaScriptTokenSource(charStream));
} else {
return new JSCommonTokenStream(new JavaScriptLexer(charStream));
}
}
}