/*
* Copyright (c) 2013 Patrick Scheibe
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.halirutan.mathematica.parsing.prattparser;
import com.intellij.lang.ASTNode;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiParser;
import com.intellij.lang.WhitespaceSkippedCallback;
import com.intellij.psi.tree.IElementType;
import de.halirutan.mathematica.parsing.prattparser.parselets.ImplicitMultiplicationParselet;
import de.halirutan.mathematica.parsing.prattparser.parselets.InfixParselet;
import de.halirutan.mathematica.parsing.prattparser.parselets.PrefixParselet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static de.halirutan.mathematica.parsing.MathematicaElementTypes.LINE_BREAK;
import static de.halirutan.mathematica.parsing.MathematicaElementTypes.WHITE_SPACES;
import static de.halirutan.mathematica.parsing.prattparser.ParseletProvider.getInfixParselet;
import static de.halirutan.mathematica.parsing.prattparser.ParseletProvider.getPrefixParselet;
/**
* @author patrick (3/27/13)
*/
public class MathematicaParser implements PsiParser {
private static final int MAX_RECURSION_DEPTH = 1024;
private static final ImplicitMultiplicationParselet IMPLICIT_MULTIPLICATION_PARSELET = new ImplicitMultiplicationParselet();
private final ImportantLineBreakHandler myImportantLinebreakHandler;
private PsiBuilder myBuilder = null;
private int myRecursionDepth;
public MathematicaParser() {
myRecursionDepth = 0;
myImportantLinebreakHandler = new ImportantLineBreakHandler();
}
/**
* Function to create information about a parsed expression. In a Pratt parser often you need the last parsed
* expression to combine it into a new parse-node. So <code >expr1 + expr2</code> is combined into a new node by
* recognizing the operator <code >+</code>, parsing <code >expr2</code> and combining everything into a new parse
* node.
* <p/>
* Since IDEA uses markers to mark the sequential code into a tree-structure I use this {@link Result} which contains
* additionally the {@link IElementType} of the last expression and whether the previous expression was parsed.
*
* @param mark
* The builder mark which was created and closed during the current parse
* @param token
* The token type of the expression which was parsed, e.g. FUNCTION_CALL_EXPRESSION
* @param parsedQ
* Whether the parsing of the expression was successful
* @return The Result object with the given parsing information.
*/
public static Result result(PsiBuilder.Marker mark, IElementType token, boolean parsedQ) {
return new Result(mark, token, parsedQ);
}
/**
* This is the return value of a parser when errors happened.
*
* @return A special return Result saying <em >the expression could not be parsed</em>. Note this does not mean that
* the expression was parsed and errors occurred! It says the parser could do absolutely nothing. This is returned if
* the parser could identify a meaningful operator from the token-stream. See {@link #parseExpression(int)} for use
* cases.
*/
public static Result notParsed() {
return new Result(null, null, false);
}
/**
* This is the main entry point for the parsing a file. Every tme
*
* @param root
* The root node of the AST
* @param builder
* Through this, the AST is built up by placing markers.
* @return The parsed AST
*/
@NotNull
@Override
public ASTNode parse(IElementType root, PsiBuilder builder) {
builder.setWhitespaceSkippedCallback(myImportantLinebreakHandler);
PsiBuilder.Marker rootMarker = builder.mark();
this.myBuilder = builder;
try {
while (!builder.eof()) {
Result expr = parseExpression();
if (!expr.isParsed()) {
builder.error("The last expression could not be parsed correctly.");
builder.advanceLexer();
}
}
rootMarker.done(root);
} catch (CriticalParserError criticalParserError) {
rootMarker.rollbackTo();
PsiBuilder.Marker newRoot = builder.mark();
final PsiBuilder.Marker errorMark = builder.mark();
while (!builder.eof()) {
builder.advanceLexer();
}
errorMark.error(criticalParserError.getMessage());
newRoot.done(root);
}
return builder.getTreeBuilt();
}
public Result parseExpression() throws CriticalParserError {
return parseExpression(0);
}
public Result parseExpression(int precedence) throws CriticalParserError {
if (myBuilder.eof()) return notParsed();
if (myRecursionDepth > MAX_RECURSION_DEPTH) {
throw new CriticalParserError("Maximal recursion depth exceeded during parsing.");
}
IElementType token = myBuilder.getTokenType();
if (token == null) {
return notParsed();
}
PrefixParselet prefix = getPrefixParselet(token);
if (prefix == null) {
return notParsed();
}
increaseRecursionDepth();
Result left = prefix.parse(this);
while (left.isParsed()) {
token = myBuilder.getTokenType();
InfixParselet infix = getInfixOrMultiplyParselet(token);
if (infix == null) {
break;
}
if (precedence >= infix.getMyPrecedence()) {
break;
}
left = infix.parse(this, left);
}
decreaseRecursionDepth();
return left;
}
@Nullable
private InfixParselet getInfixOrMultiplyParselet(IElementType token) {
InfixParselet infixParselet = getInfixParselet(token);
PrefixParselet prefixParselet = getPrefixParselet(token);
if (infixParselet != null) return infixParselet;
if (prefixParselet == null) {
return null;
}
if (myImportantLinebreakHandler.hadLineBreak()) {
return null;
}
return IMPLICIT_MULTIPLICATION_PARSELET;
}
public int decreaseRecursionDepth() {
return --myRecursionDepth;
}
public int increaseRecursionDepth() {
return ++myRecursionDepth;
}
public PsiBuilder.Marker mark() {
return myBuilder.mark();
}
public IElementType getTokenType() {
return myBuilder.getTokenType();
}
public void advanceLexer() throws CriticalParserError {
if (myBuilder.eof()) {
myBuilder.error("More input expected");
throw new CriticalParserError("Unexpected end of input.");
}
myImportantLinebreakHandler.reset();
myBuilder.advanceLexer();
}
public boolean matchesToken(IElementType token) {
final IElementType testToken = myBuilder.getTokenType();
return (testToken != null && testToken.equals(token));
}
public boolean matchesToken(IElementType token1, IElementType token2) {
final IElementType firstToken = myBuilder.lookAhead(0);
final IElementType secondToken = myBuilder.lookAhead(1);
return (firstToken != null && firstToken.equals(token1)) && (secondToken != null && secondToken.equals(token2));
}
/**
* Wrapper for {@link PsiBuilder#error(String)}
*
* @param s
* Error message
*/
public void error(String s) {
myBuilder.error(s);
}
@SuppressWarnings({"BooleanMethodNameMustStartWithQuestion", "InstanceMethodNamingConvention"})
public boolean eof() {
return myBuilder.eof();
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isNextWhitespace() {
final IElementType possibleWhitespace = myBuilder.rawLookup(1);
return WHITE_SPACES.contains(possibleWhitespace);
}
/**
* For the Pratt parser we need the left side which was already parsed. An instance of this will provide all necessary
* information required to know what expression was parsed on the left of an infix operator.
*/
public static final class Result {
private final PsiBuilder.Marker myLeftMark;
private final IElementType myLeftToken;
private final boolean myParsed;
private Result(PsiBuilder.Marker leftMark, IElementType leftToken, boolean parsed) {
this.myLeftMark = leftMark;
this.myLeftToken = leftToken;
this.myParsed = parsed;
}
public PsiBuilder.Marker getMark() {
return myLeftMark;
}
public IElementType getToken() {
return myLeftToken;
}
/**
* True, iff an expression could be parsed correctly. This method can be used to check, whether the result of the
* parsing of a sub-expression was successful. For instance in <code >expr1 + expr2</code>: you can test if <code
* >expr2</code> was parsed successfully and decide what to do in the parsing of Plus, if it wasn't
*
* @return true if an expression was parsed correctly.
*/
public boolean isParsed() {
return myParsed;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean isValid() {
return (myLeftMark != null) && (myLeftToken != null);
}
}
/**
* Registers when a whitespace token was seen. This is important in order to find out whether an <em>implicit
* multiplication</em> has arisen.
*/
public class ImportantLineBreakHandler implements WhitespaceSkippedCallback {
private boolean myLineBreakSeen = false;
@Override
public void onSkip(IElementType type, int start, int end) {
if (type.equals(LINE_BREAK)) myLineBreakSeen = true;
}
public void reset() {
myLineBreakSeen = false;
}
public boolean hadLineBreak() {
return myLineBreakSeen;
}
}
}