Table of Contents
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 ^
