Question
Exercise 2 The following two java classes implement a simple calculator that understands a language of numeric expressions. The language allows numeric constants; the operators
Exercise 2 The following two java classes implement a simple calculator that understands a language of numeric expressions. The language allows numeric constants; the operators +,-,*,and / ; and parentheses. The calculator observes proper precedence and associativity. The first class, CalcLexer, divides a string containing an expression into a sequence of tokens. The class CalcParser parses that sequence of tokens according to a grammer. CalcParser uses a style of parsing called recursive descent, which uses a different method for each non-terminal symbol in the grammer. If you look at the methods parseExpression, parseMulexp, and parseRootexp you will recognize the productions of EBNF grammers like putes the value of the resulting expression as it parse. Modify both CalcParser and CalcLexer so that they handle errors internally by throwing checked execptions. To get started, create a new class CalcError that extends Exception. Whenever an error is first detected, modify the code to make it throw new CalcError(message), where message is the error message you want to use. Do not change the interface CalcParser presents to its clients; the client of CalcParser should not recieve CalcError execeptions and should still use the getErrorMessage method of the CalcParser to check for errors. But internally, you should be able to eliminate alomst all other references to the errorMessage instance variable in CalcParser, and yo should be able to entirely eliminate the errorMessage instance variable in CalcLexer. Hint: the program should become noticeable shorter and simpler as a rsult. if not-if it seems to be getting longer and more complicated-you are doing something wrong.
CalcLexer class
/** * A CalcLexer provides a simple scanner for a * CalcParser. We hold the string being parsed, and the * CalcParser uses us to read the string as a sequence * of tokens. */ public class CalcLexer { /** * The string being parsed, held in a * StringTokenizer. */ private java.util.StringTokenizer tokens; /** * The error message. This will be null if there * has been no error. */ private String errorMessage = null; /** * The current token. */ private int tokenChar; /** * If the current token is NUMBER_TOKEN, this is * the number in question. */ private double tokenNum; /** * Non-character values for tokenChar. By choosing * negative values we are certain not to collide * with any char values stored in the int tokenChar. */ public static final int NUMBER_TOKEN = -1; public static final int EOLN_TOKEN = -2; /** * Constructor for a CalcLexer. Our parameter is the * string to be tokenized. * @param s the String to be tokenized */ public CalcLexer(String s) { // We use a StringTokenizer to tokenize the string. // Our delimiters are the operators, parens, and // white space. By making the third parameter true // we instruct the StringTokenizer to return those // delimiters as tokens. tokens = new java.util.StringTokenizer( s," \t +-*/()",true); // Start by advancing to the first token. Note that // this may get an error, which would set our // errorMessage instead of setting tokenChar. advance(); } /** * Advance to the next token. We don't return * anything; the caller must use nextToken() to see * what that token is. */ public void advance() { // White space is returned as a token by our // StringTokenizer, but we will loop until something // other than white space has been found. while (true) { // If we're at the end, make it an EOLN_TOKEN. if (!tokens.hasMoreTokens()) { tokenChar = EOLN_TOKEN; return; } // Get a token--if it looks like a number, // make it a NUMBER_TOKEN. String s = tokens.nextToken(); char c1 = s.charAt(0); if (s.length()>1 || Character.isDigit(c1)) { try { tokenNum = Double.valueOf(s).doubleValue(); tokenChar = NUMBER_TOKEN; } catch (NumberFormatException x) { errorMessage = "Illegal format for a number."; } return; } // Any other single character that is not // white space is a token. else if (!Character.isWhitespace(c1)) { tokenChar = c1; return; } } } /** * Return our error message. This will be null if no * error has occurred. * * @return error String or null if no error */ public String getErrorMessage() { return errorMessage; } /** * Return the value of a numeric token. This should * only be called when nextToken() reports a * NUMBER_TOKEN. * * @return the double value of the number */ public double getNum() { return tokenNum; } /** * Return the next token. Repeated calls will * return the same token again; the caller should * use advance() to advance to another token. * @return the next token as an int */ public int nextToken() { return tokenChar; } }
CalcParser class
/** * A CalcParser is a calculator that evaluates a String * containing a numeric expression. We handle numbers, * the operators +,-,*, and / with the usual precedence * and associativity, and parentheses. */ public class CalcParser { /** * We use a CalcLexer object to tokenize the input * string. */ private CalcLexer lexer; /** * Our error message or null if there has been * no error. */ private String errorMessage = null; /** * The result of evaluating the expression (if * no error). */ private double value; /** * Constructor for CalcParser. This actually does * all the work. We parse and evaluate the string from * here. Our caller should then use the * getErrorMessage() method to see if there has been * an error and, if not, the getValue() method to get * the value we calculated. * * @param s the string to be parsed */ public CalcParser(String s) { // First make a CalcLexer to hold the string. This // will get an error immediately if the first token // is bad, so check for that. lexer = new CalcLexer(s); errorMessage = lexer.getErrorMessage(); if (errorMessage != null) return; // Now parse the expression and get the result. value = parseExpression(); if (errorMessage != null) return; // After the expression we should be at the end of // the input. match(CalcLexer.EOLN_TOKEN); if (errorMessage != null) return; } /** * Get the error message or null if none. * * @return the error message or null */ public String getErrorMessage() { return errorMessage; } /** * Get the value of the expression as a string. This * should only be called if getErrorMessage() returned * null. * * @return the value of the expression as a String */ public String getValue() { return Double.toString(value); } /** * Match a given token and advance to the next. * This utility is used by our parsing routines. * If the given token does not match * lexer.nextToken(), we generate an appropriate * error message. Advancing to the next token may * also cause an error. * * @param token the token that must match */ private void match(int token) { // First check that the current token matches the // one we were passed; if not, make an error. if (lexer.nextToken() != token) { if (token == CalcLexer.EOLN_TOKEN) errorMessage = "Unexpected text after the expression."; else if (token == CalcLexer.NUMBER_TOKEN) errorMessage = "Expected a number."; else errorMessage = "Expected a " + ((char) token) + "."; return; } // Now advance to the next token. lexer.advance(); errorMessage = lexer.getErrorMessage(); } /** * Parse an expression. If any error occurs we * return immediately. * * @return the double value of the expression * or garbage in case of errors. */ private double parseExpression() { //::= // { ('+' ) | ('-' ) } double result = parseMulexp(); if (errorMessage != null) return result; while (true) { if (lexer.nextToken() == '+') { match('+'); if (errorMessage != null) return result; result += parseMulexp(); if (errorMessage != null) return result; } else if (lexer.nextToken() == '-') { match('-'); if (errorMessage != null) return result; result -= parseMulexp(); if (errorMessage != null) return result; } else return result; } } /** * Parse a mulexp, a subexpression at the precedence * level of * and /. If any error occurs we return * immediately. * * @return the double value of the mulexp or * garbage in case of errors. */ private double parseMulexp() { // ::= // { ('*' ) | ('/' ) } double result = parseRootexp(); if (errorMessage != null) return result; while (true) { if (lexer.nextToken() == '*') { match('*'); if (errorMessage != null) return result; result *= parseRootexp(); if (errorMessage != null) return result; } else if (lexer.nextToken() == '/') { match('/'); if (errorMessage != null) return result; result /= parseRootexp(); if (errorMessage != null) return result; } else return result; } } /** * Parse a rootexp, which is a constant or * parenthesized subexpression. If any error occurs * we return immediately. * * @return the double value of the rootexp or garbage * in case of errors */ private double parseRootexp() { double result = 0.0; // ::= '(' ')' if (lexer.nextToken() == '(') { match('('); if (errorMessage != null) return result; result = parseExpression(); if (errorMessage != null) return result; match(')'); if (errorMessage != null) return result; } // ::= number else if (lexer.nextToken()==CalcLexer.NUMBER_TOKEN){ result = lexer.getNum(); if (errorMessage != null) return result; match(CalcLexer.NUMBER_TOKEN); if (errorMessage != null) return result; } else { errorMessage = "Expected a number or a parenthesis."; } return result; } }
Step by Step Solution
There are 3 Steps involved in it
Step: 1
Get Instant Access to Expert-Tailored Solutions
See step-by-step solutions with expert insights and AI powered tools for academic success
Step: 2
Step: 3
Ace Your Homework with AI
Get the answers you need in no time with our AI-driven, step-by-step assistance
Get Started