/******************************************************************************* * 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)); } } }