Package de.halirutan.mathematica.parsing.prattparser

Source Code of de.halirutan.mathematica.parsing.prattparser.MathematicaParser$ImportantLineBreakHandler

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

  }
}
TOP

Related Classes of de.halirutan.mathematica.parsing.prattparser.MathematicaParser$ImportantLineBreakHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.