Here is an example of how to set up an exception hierarchy.

The application is a simple expression parser.

Token

Here’s the Token class, which represents the known tokens for the parser to consume.

package parser;

/**
 * Class to represent a token for the parser
 * 
 * @author Bryan
 */
public class Token
{
  /**
   * The allowed token types
   */
  public static enum TYPE
  {
    UNDEFINED,
    NUMBER,                         // numeric literal
    PLUS, MINUS, MULTIPLY, DIVIDE,  // arithmetic operators
    REMAINDER, EXPONENTIATION,      // more arirthmetic operators
    OPEN_PAREN, CLOSE_PAREN,        // open and close parentheses
    END_OF_INPUT                    // Special token to flag end of input
  };

  /**
   * Returns the type of the token.
   * 
   * @return token type
   */
  public TYPE getType()
  {
    return m_type;
  }
  
  /**
   * Sets the type of the token
   * 
   * @param type The type to set
   */
  void setType(TYPE type)
  {
    m_type = type;
  }
  
  /**
   * Get the token string value
   */
  public String getValue()
  {
    return m_value;
  }
  
  /**
   * Sets the token string value
   * 
   * @param value
   */
  public void setValue(String value)
  {
    m_value = value;
  }

  /**
   * Gets the token offset within the line
   */
  public int getOffset()
  {
    return m_offset;
  }
  
  /**
   * Sets the token offset within the line
   * 
   * @param offset
   */
  public void setOffset(int offset)
  {
    m_offset = offset;
  }

  /**
   * Return a usable string
   */
  @Override
  public String toString()
  {
    return "Token " + getType() + " [" + getValue() + "]" +
           " Offset: " + getOffset();
  }
  
  //// Private data ////
  private TYPE m_type = TYPE.UNDEFINED; // The token type
  private String m_value = "";          // The token string value
  private int m_offset = 0;             // The offset of the token in the line
  
  /**
   * Main entry point, for testing
   */
  public static void main(String[] args)
  {
    Token token = new Token();
    token.setType(Token.TYPE.EXPONENTIATION);
    token.setValue("^");
    System.out.println(token);
  }
}

Parser

Here’s the source code for the expression Parser:

package parser;

import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;

/**
  This parser supports an expression represented by
  the following grammar:

   expression = term {("+"|"-") term}

   term = factor {("*"|"/"|"%") factor}

   factor = subfactor ["^" subfactor]

   subfactor = ["+"|"-"] subexpression
 
   subexpression = "(" expression ")" | atom

   atom = number

*/

/**
 * Class to implement a recursive-descent expression parser.
 * 
 * @author Bryan J. Higgs
 */
public class Parser
{
  /**
   * Constructor
   */
  public Parser()
  {}
  
  /**
   * Gets the expression string being parsed.
   */
  public String getExpression()
  {
    return m_expression;
  }

  /**
   * Gets the current token for the parse
   * (changes as the parse state changes)
   */
  public Token getCurrentToken()
  {
    return m_currentToken;
  }
  
  /**
   * Top-level parse entry point
   */
  public double parse(String expression) throws ParserException
  {
    System.err.println("\nStarting parse on [" + expression + "]...");

    m_expression = expression;    
    m_reader = new StringReader(expression);
    try
    {
      m_tokenizer = new StreamTokenizer(m_reader);
      m_tokenizer.ordinaryChar('/'); 
                   // Ensure that '/' are not considered comment chars
      //m_tokenizer.slashSlashComments(true);
      //m_tokenizer.slashStarComments(true);
      //m_tokenizer.eolIsSignificant(false);

      nextToken(); // Get the first token from the input.

      double result = parseExpression(); // Parse the expression until the end.

      if (m_currentToken.getType() != Token.TYPE.END_OF_INPUT)
      {
          throw new UnexpectedExtraTokenException(m_currentToken, 
                                                  m_expression);
      }

      System.err.println("Value of [" + expression + "] : " + result);
      return result;
    }
    finally
    {
      // Close the reader, regardless
      m_reader.close();
    }
  }

  //// Private methods ////
  
  /**
   * Returns the next token encountered in the expression
   */
  private void nextToken() throws ParserException
  {
    // Set token to unknown, in case something goes awry
    m_currentToken.setType(Token.TYPE.UNDEFINED);
    m_currentToken.setValue("");
    
    try
    {
      int token = m_tokenizer.nextToken();
      switch (token)
      {
        case StreamTokenizer.TT_NUMBER:
          System.err.println("Token: [Number] " + m_tokenizer.nval);
          m_currentToken.setType(Token.TYPE.NUMBER);
          m_currentToken.setValue("" + m_tokenizer.nval);          
          break;
          
        case StreamTokenizer.TT_EOF:
          // We came to the end of the stream
          System.err.println("Token: [END_OF_INPUT]");
          m_currentToken.setType(Token.TYPE.END_OF_INPUT);
          m_currentToken.setValue("");
          break;
          
        case '+':
          System.err.println("Token: [PLUS]");
          m_currentToken.setType(Token.TYPE.PLUS);
          m_currentToken.setValue("+");
          break;
        case '-':
          System.err.println("Token: [MINUS]");
          m_currentToken.setType(Token.TYPE.MINUS);
          m_currentToken.setValue("-");
          break;
        case '*':
          System.err.println("Token: [MULTIPLY]");
          m_currentToken.setType(Token.TYPE.MULTIPLY);
          m_currentToken.setValue("*");
          break;
        case '/':
          System.err.println("Token: [DIVIDE]");
          m_currentToken.setType(Token.TYPE.DIVIDE);
          m_currentToken.setValue("/");
          break;
        case '%':
          System.err.println("Token: [REMAINDER]");
          m_currentToken.setType(Token.TYPE.REMAINDER);
          m_currentToken.setValue("%");
          break;
        case '^':
          System.err.println("Token: [EXPONENTIATION]");
          m_currentToken.setType(Token.TYPE.EXPONENTIATION);
          m_currentToken.setValue("^");
          break;
        case '(':
          System.err.println("Token: [OPEN_PAREN]");
          m_currentToken.setType(Token.TYPE.OPEN_PAREN);
          m_currentToken.setValue("(");
          break;
        case ')':
          System.err.println("Token: [CLOSE_PAREN]");
          m_currentToken.setType(Token.TYPE.CLOSE_PAREN);
          m_currentToken.setValue(")");
          break;
          
        case StreamTokenizer.TT_WORD:
          m_currentToken.setValue(m_tokenizer.sval);
          throw new UnsupportedFeatureException(m_currentToken, m_expression,
                                                "Variable name");
          // break;
          
        default:
          System.err.println("Stream Token: " + token + 
                             " '" + (char) (token) + "'");
          break;
      }
    
      // If we failed to get a valid token, report it...
      if (m_currentToken.getType() == Token.TYPE.UNDEFINED)
      {
          throw new UnexpectedTokenFoundException(m_currentToken,
                                                  m_expression);
      }
    }
    catch (IOException ioe)
    {
      throw new ParserIOException(getExpression(), 0, ioe);
    }
  }

  /**
   * Parse an expression
   * expression = term { ("+"|"-") term }
   */
  private double parseExpression() throws ParserException
  {
    System.err.println("In expression...");
    double result = parseTerm();
    System.err.println("Back in expression...");

    for ( Token.TYPE type = m_currentToken.getType();
          ( type == Token.TYPE.PLUS || type == Token.TYPE.MINUS );
          type = m_currentToken.getType()
        )
    {
        nextToken();

        double temp = parseTerm();
        System.err.println("Back in expression...");
        
        switch (type)
        {
        case PLUS:
            result += temp;
            break;
        case MINUS:
            result -= temp;
            break;
        }
    }
    return result;
  }

  /**
   * Parse a term
   * term = factor { ("*"|"/"|"%") factor }
   */
  private double parseTerm() throws ParserException
  {
    System.err.println("In term...");
    double result = parseFactor();
    System.err.println("Back in term...");

    for ( Token.TYPE type = m_currentToken.getType();
          ( type == Token.TYPE.MULTIPLY ||
            type == Token.TYPE.DIVIDE || type == Token.TYPE.REMAINDER );
          type = m_currentToken.getType()
        )
    {
        nextToken();

        double temp = parseFactor();
        System.err.println("Back in term...");
        
        switch (type)
        {
        case MULTIPLY:
            result *= temp;
            break;
        case DIVIDE:
            if (temp == 0.0)
            {
                throw new DivisionByZeroException( m_expression,
                                                   0 // Offset
                                                 );
            }
            result /= temp;
            break;
        case REMAINDER:
            if (temp == 0.0)
            {
                throw new DivisionByZeroException( m_expression,
                                                   0 // Offset
                                                 );
            }
            result = (long) result % (long) temp;
            break;
        }
    }

    return result;
  }

  /**
   * Parse a factor
   * factor = subfactor ["^" subfactor]
   */
  private double parseFactor() throws ParserException
  {
    System.err.println("In factor...");
    double result = parseSubfactor();
    System.err.println("Back in factor...");
    
    Token.TYPE type = m_currentToken.getType();
    if ( type == Token.TYPE.EXPONENTIATION )
    {
        nextToken();

        double temp = parseSubfactor();
        System.err.println("Back in factor...");

        result = Math.pow(result, temp);
    }
    return result;
  }

  /**
   * Parse a 'subfactor'
   * subfactor = ["+"|"-"] subexpression
   */
  private double parseSubfactor() throws ParserException
  {
    System.err.println("In subfactor...");
    
    Token.TYPE type = m_currentToken.getType();
    if ( type == Token.TYPE.PLUS || type == Token.TYPE.MINUS )
    {
        nextToken();
    }

    double result = parseSubexpression();
    System.err.println("Back in subfactor...");
    
    switch (type)
    {
    case MINUS:
        result = -result;
        break;
    case PLUS:
        // Do nothing
    }
    return result;
  }

  /**
   * Parse precedence level 5
   * subexpression = "(" expression ")" | atom
   */
  private double parseSubexpression() throws ParserException
  {
    System.err.println("In subexpression...");
    
    double result = 0.0;
    
    Token.TYPE type = m_currentToken.getType();
    if ( type == Token.TYPE.OPEN_PAREN )
    {
        nextToken();

        result = parseExpression();
        System.err.println("Back in subexpression...");

        type = m_currentToken.getType();
        if (type != Token.TYPE.CLOSE_PAREN)
        {
            throw new UnbalancedParenthesesException( m_expression,
                                                      0 // Offset
                                                    );
        }

        nextToken();
    }
    else
    {
        result = parseAtom();
        System.err.println("Back in subexpression...");
    }
    
    return result;
  }

  /**
   * Parse atoms
   * atom = number
   */
  private double parseAtom() throws ParserException
  {
    System.err.println("In atom...");
    
    double result = 0.0;
    
    Token.TYPE type = m_currentToken.getType();
    switch (type)
    {
    case NUMBER:
        try
        {
          result = Double.parseDouble( m_currentToken.getValue() );
        }
        catch (NumberFormatException nfe)
        {
          throw new ParserNumberFormatException(m_currentToken,
                                                m_expression, nfe);
        }
        nextToken();
        break;
        
    default:
        throw new UnexpectedTokenFoundException( m_currentToken,
                                                 m_expression);
    }
    return result;
  }
  
  /**
   * Convenient output for Parser
   * 
   * @return a string representation for the Parser object
   */
  @Override
  public String toString()
  {
    return "Parser: " + getExpression() + "\n" +
                        getCurrentToken();
  }
  
  /**
   * Test Expression method for testing
   * 
   * @param expression
   */
  public static void testExpr(String expression)
  {
    try
    {
      double result = m_parser.parse(expression);
      System.err.println("Result of '" + m_parser.getExpression() + 
                         "' = " + result);
    }
    catch (ParserException pe)
    {
        System.err.println(">>> " + pe.getFormattedMessage());
    }
  }

  /**
   * Main entry point for testing
   * 
   * @param args the command line arguments
   */
  public static void main(String[] args)
  {
    m_parser = new Parser();
    testExpr("1+2*3/4");
    testExpr("  1 + 2 * 3");
    testExpr("3^3");
    testExpr("(1 + 2) * 3");
    testExpr("53%5");
    testExpr("72^0");
    testExpr("+5 + -6 * -(3 * 4)");
    testExpr(" X + 1");
    testExpr("11 - 6 7");
    testExpr(" 3 - +(2 + 4  ");
    testExpr("4 + 5/0");
    testExpr("10 ** 8");
    testExpr("(10-5)*9)");
    testExpr("/8");
  }
  
  //// Private data ////
  private String m_expression = "";
  private StringReader m_reader;
  private StreamTokenizer m_tokenizer;
  private Token m_currentToken = new Token(); // UNDEFINED token type
  
  private static Parser m_parser;
}

Exceptions

The exceptions that this parser uses form a hierarchy:

ParserException
  DivisionByZeroException
  ParserIOException
  ParserNumberFormatException
  UnbalancedParenthesesException
  UnexpectedExtraTokenException
  UnexpectedTokenFoundException
  UnsupportedFeatureException

By structuring these exceptions in this way, we can simplify the parser code.

ParserException

package parser;

/**
 * Class to act as the superclass of all Parser exceptions.
 *
 * @author Bryan J. Higgs
 */
public abstract class ParserException extends Exception
{
  /**
   * Gets the message associated with an exception.
   * (Abstract method)
   */
  @Override
  public abstract String getMessage();

  /**
   * Gets the Line of source where the exception occurred
   */
  public String getLine()
  {
    return m_line;
  }

  /**
   * Gets the character offset within the line where the
   * exception occurred.
   */
  public int getOffset()
  {
    return m_offset;
  }

  /**
   * Gets a formatted message of the form:
   *
   * <message>
   * <contents of line>
   *       ^
   * (the above caret is aligned with the character
   *  corresponding to the offset within the line).
   */
  public String getFormattedMessage()
  {
    String msg = getMessage() + "\n";
    msg += getLine() + "\n";
    for (int i = 0; i < getOffset(); i++)
    {
      msg += " ";
    }
    msg += "^\n"; // The caret (^) should point to the error location.

    return msg;
  }

  /**
   * Constructor
   * (protected because it's only used by derived classes)
   */
  protected ParserException(String line, int offset)
  {
    m_line = line;
    m_offset = offset;
  }

  /// Private data ///
  private String  m_line;   // The line containing the expression
  private int     m_offset; // The offset within the line
}

DivisionByZeroException

package parser;

/**
 * Exception thrown when a division by zero was detected by the parser.
 *
 * @author Bryan J. Higgs
 */
public class DivisionByZeroException extends ParserException
{
  /**
   * Constructor
   */
  public DivisionByZeroException(String line, int offset)
  {
    super(line, offset);
  }

  /**
   * Gets the message for the exception
   * (implementation of abstract method)
   */
  public String getMessage()
  {
    return "Division by zero attempted";
  }
}

ParserIOException

package parser;

import java.io.IOException;

/**
 * Class to represent an IOException when encountered in the parser
 *
 * @author Bryan J. Higgs
 */
public class ParserIOException extends ParserException
{
  public ParserIOException(String line, int offset, IOException ioe)
  {
    super(line, offset);
    m_ioe = ioe;
  }

  @Override
  public String getMessage()
  {
    return "An IOException occurred during parsing";
  }

  /// Private data ///
  private IOException m_ioe;
}

ParserNumberFormatException

package parser;

/**
 * Class to represent a NumberFormatException that occurred
 * during parsing.
 *
 * @author Bryan J. Higgs
 */
public class ParserNumberFormatException extends ParserException
{
  public ParserNumberFormatException(Token token, String line, 
                                     NumberFormatException nfe)
  {
    super(line, token.getOffset());
    m_token = token;
    m_nfe = nfe;
  }

  @Override
  public String getMessage()
  {
    return "A NumberFormatException occurred during parsing";
  }

  /// Private data ///
  private Token m_token;
  private NumberFormatException m_nfe;
}

UnbalancedParenthesesException

package parser;

/**
 * Exception thrown when unbalanced parentheses were detected by the parser.
 *
 * @author Bryan J. Higgs
 */
public class UnbalancedParenthesesException extends ParserException
{
  /**
   * Constructor
   */
  public UnbalancedParenthesesException(String line, int offset)
  {
    super(line, offset);
  }

  /**
   * Gets the message for the exception
   * (implementation of abstract method)
   */
  public String getMessage()
  {
    return "Unbalanced parentheses";
  }
}

UnexpectedExtraTokenException

package parser;

/**
 * Exception thrown when an unexpected extra token was detected by the parser.
 *
 * @author Bryan J. Higgs
 */
public class UnexpectedExtraTokenException extends ParserException
{
  /**
   * Constructor
   */
  public UnexpectedExtraTokenException(Token token, String line)
  {
    super(line, token.getOffset());
    m_token = token;
  }

  /**
   * Gets the message for the exception
   * (implementation of abstract method)
   */
  public String getMessage()
  {
    return "Unexpected token after end of expression: " + m_token;
  }

  /// Private data ///
  private Token m_token;
}

UnexpectedTokenFoundException

package parser;

/**
 * Exception thrown when no expression was found by the parser.
 *
 * @author Bryan J. Higgs
 */
public class UnexpectedTokenFoundException extends ParserException
{
  /**
   * Constructor
   */
  public UnexpectedTokenFoundException(Token token, String line)
  {
    super(line, token.getOffset());
    m_token = token;
  }

  /**
   * Gets the message for the exception
   * (implementation of abstract method)
   */
  public String getMessage()
  {
    return "Unexpected token encountered " + m_token;
  }

  //// Private data ////
  private Token m_token;
}

UnsupportedFeatureException

package parser;

/**
 * Class to represent an unsupported feature exception
 * found during parsing.
 *
 * @author Bryan J. Higgs
 */
public class UnsupportedFeatureException extends ParserException
{
  public UnsupportedFeatureException(Token token, String line, String msg)
  {
    super(line, token.getOffset());
    m_token = token;
    m_msg = msg;
  }
  
  @Override
  public String getMessage()
  {
    return "Unsupported feature: " + m_msg;
  }

  //// Private data ////
  private Token m_token;
  private String m_msg;
}

Results

Here is what the expression parser produces for output, from the test cases supplied from the Parser‘s main method:

Parsing [1+2*3/4]

Starting parse on [1+2*3/4]...
Token: [Number] 1.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [PLUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [Number] 2.0
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [MULTIPLY]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [Number] 3.0
In factor...
In subfactor...
In subexpression...
In atom...
Token: [DIVIDE]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [Number] 4.0
In factor...
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Value of [1+2*3/4] : 2.5
Result of '1+2*3/4' = 2.5

Parsing [ 1 + 2 * 3]

Starting parse on [  1 + 2 * 3]...
Token: [Number] 1.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [PLUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [Number] 2.0
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [MULTIPLY]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [Number] 3.0
In factor...
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Value of [  1 + 2 * 3] : 7.0
Result of '  1 + 2 * 3' = 7.0

Parsing [3^3]

Starting parse on [3^3]...
Token: [Number] 3.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [EXPONENTIATION]
Back in subexpression...
Back in subfactor...
Back in factor...
Token: [Number] 3.0
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Value of [3^3] : 27.0
Result of '3^3' = 27.0

Parsing [(1 + 2) * 3]

Starting parse on [(1 + 2) * 3]...
Token: [OPEN_PAREN]
In expression...
In term...
In factor...
In subfactor...
In subexpression...
Token: [Number] 1.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [PLUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [Number] 2.0
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [CLOSE_PAREN]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Back in subexpression...
Token: [MULTIPLY]
Back in subfactor...
Back in factor...
Back in term...
Token: [Number] 3.0
In factor...
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Value of [(1 + 2) * 3] : 9.0
Result of '(1 + 2) * 3' = 9.0

Parsing [53%5]

Starting parse on [53%5]...
Token: [Number] 53.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [REMAINDER]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [Number] 5.0
In factor...
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Value of [53%5] : 3.0
Result of '53%5' = 3.0

Parsing [72^0]

Starting parse on [72^0]...
Token: [Number] 72.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [EXPONENTIATION]
Back in subexpression...
Back in subfactor...
Back in factor...
Token: [Number] 0.0
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Value of [72^0] : 1.0
Result of '72^0' = 1.0

Parsing [+5 + -6 * -(3 * 4)]

Starting parse on [+5 + -6 * -(3 * 4)]...
Token: [PLUS]
In expression...
In term...
In factor...
In subfactor...
Token: [Number] 5.0
In subexpression...
In atom...
Token: [PLUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [Number] -6.0
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [MULTIPLY]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [MINUS]
In factor...
In subfactor...
Token: [OPEN_PAREN]
In subexpression...
Token: [Number] 3.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [MULTIPLY]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [Number] 4.0
In factor...
In subfactor...
In subexpression...
In atom...
Token: [CLOSE_PAREN]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Back in subexpression...
Token: [END_OF_INPUT]
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Value of [+5 + -6 * -(3 * 4)] : 77.0
Result of '+5 + -6 * -(3 * 4)' = 77.0

Parsing [ X + 1]

Starting parse on [ X + 1]...
>>> Unsupported feature: Variable name
 X + 1
^

Parsing [11 – 6 7]

Starting parse on [11 - 6 7]...
Token: [Number] 11.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [MINUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [Number] 6.0
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [Number] 7.0
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
>>> Unexpected token after end of expression: Token NUMBER [7.0] Offset: 0
11 - 6 7
^

Parsing [ 3 – +(2 + 4 ]

Starting parse on [ 3 - +(2 + 4  ]...
Token: [Number] 3.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [MINUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [PLUS]
In term...
In factor...
In subfactor...
Token: [OPEN_PAREN]
In subexpression...
Token: [Number] 2.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [PLUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [Number] 4.0
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Back in subexpression...
>>> Unbalanced parentheses
 3 - +(2 + 4  
^

Parsing [4 + 5/0]

Starting parse on [4 + 5/0]...
Token: [Number] 4.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [PLUS]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Token: [Number] 5.0
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [DIVIDE]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [Number] 0.0
In factor...
In subfactor...
In subexpression...
In atom...
Token: [END_OF_INPUT]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
>>> Division by zero attempted
4 + 5/0
^

Parsing [10 ** 8]

Starting parse on [10 ** 8]...
Token: [Number] 10.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [MULTIPLY]
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Token: [MULTIPLY]
In factor...
In subfactor...
In subexpression...
In atom...
>>> Unexpected token encountered Token MULTIPLY [*] Offset: 0
10 ** 8
^

Parsing [(10-5)*9)]

Starting parse on [(10-5)*9)]...
Token: [OPEN_PAREN]
In expression...
In term...
In factor...
In subfactor...
In subexpression...
Token: [Number] 10.0
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
Token: [Number] -5.0
Back in subexpression...
Back in subfactor...
Back in factor...
Back in term...
Back in expression...
Back in subexpression...
>>> Unbalanced parentheses
(10-5)*9)

Parsing [/8]

Starting parse on [/8]...
Token: [DIVIDE]
In expression...
In term...
In factor...
In subfactor...
In subexpression...
In atom...
>>> Unexpected token encountered Token DIVIDE [/] Offset: 0
/8
^