// JavaAnalyzer class interface: check for balanced symbols // // CONSTRUCTION: with a BufferedReader object // ******************PUBLIC OPERATIONS*********************** // int checkBalance( ) --> Print mismatches // return number of errors // ******************ERRORS********************************** // Error checking on comments and quotes is performed // main checks for balanced symbols. import java.io.*; import DataStructures.*; import Exceptions.*; import Supporting.*; import Supporting.Comparable; /** * Symbol represents what will be placed on the stack. */ class Symbol { char token; int theLine; Symbol( char tok, int line ) { token = tok; theLine = line; } } /** * Class to perform symbol matching * generation for Java programs. */ class JavaAnalyzer { /** * Constructor. * @param inStream the stream containing a program. */ public JavaAnalyzer( PushbackReader inStream ) { errors = 0; ch = '\0'; currentLine = 1; in = inStream; pendingTokens = new StackAr( ); } /** * Print an error message for unbalanced symbols. * @return number of errors detected. */ public int checkBalance( ) { Symbol match = null; errors = 0; currentLine = 1; while( getNextSymbol( ) ) { char lastChar = ch; Symbol lastSymbol = new Symbol( lastChar, currentLine ); switch( lastChar ) { case '(': case '[': case '{': pendingTokens.push( lastSymbol ); break; case ')': case ']': case '}': try { match = (Symbol) pendingTokens.topAndPop( ); checkMatch( match, lastSymbol ); } catch( Underflow e ) { errors++; System.out.println( "Extraneous " + lastChar + " at line " + currentLine ); } break; default: // Cannot happen break; } } while( !pendingTokens.isEmpty( ) ) { errors++; try { match = (Symbol) pendingTokens.topAndPop( ); } catch( Underflow e ) { } // Cannot happen System.out.println( "Unmatched " + match.token + " at line " + match.theLine ); } return errors; } private static final int SLASH_SLASH = 0; private static final int SLASH_STAR = 1; private PushbackReader in; // The input stream private char ch; // Current character private int currentLine; // Current line private Stack pendingTokens; // Open symbols pending private int errors; // Number of errors seen /** * nextChar sets ch based on the next character in the input stream. * putBackChar puts the character back onto the stream. * It should only be used once after a nextChar. * Both routines adjust currentLine if necessary. */ private boolean nextChar( ) { try { int readVal = in.read( ); if( readVal == -1 ) return false; ch = (char) readVal; if( ch == '\n' ) currentLine++; return true; } catch( IOException e ) { return false; } } private void putBackChar( ) { if( ch == '\n' ) currentLine--; try { in.unread( (int) ch ); } catch( IOException e ) { } } /** * Precondition: We are about to process a comment; have already seen * comment-start token * Postcondition: Stream will be set immediately after * comment-ending token */ private void skipComment( int start ) { if( start == SLASH_SLASH ) { while( nextChar( ) && ( ch != '\n' ) ) ; return; } // Look for a */ sequence boolean state = false; // True if we have seen * while( nextChar( ) ) { if( state && ch == '/' ) return; state = ( ch == '*' ); } errors++; System.out.println( "Unterminated comment!" ); } /** * Precondition: We are about to process a quote; have already seen * beginning quote. * Postcondition: Stream will be set immediately after * matching quote */ private void skipQuote( char quoteType ) { while( nextChar( ) ) { if( ch == quoteType ) return; if( ch == '\n' ) { errors++; System.out.println( "Missing closed quote at line " + currentLine ); return; } else if( ch == '\\' ) nextChar( ); } } /** * Get the next opening or closing symbol. * Return false if end of file. * Skip past comments and character and string constants */ private boolean getNextSymbol( ) { while( nextChar( ) ) { if( ch == '/' ) processSlash( ); else if( ch == '\'' || ch == '"' ) skipQuote( ch ); else if( ch == '\\' ) // Extra case, not in text nextChar( ); else if( ch == '(' || ch == '[' || ch == '{' || ch == ')' || ch == ']' || ch == '}' ) return true; } return false; // End of file } /** * Print an error message if clSym does not match opSym. * Update Error. */ private void checkMatch( Symbol opSym, Symbol clSym ) { if( opSym.token == '(' && clSym.token != ')' || opSym.token == '[' && clSym.token != ']' || opSym.token == '{' && clSym.token != '}' ) { System.out.println( "Found " + clSym.token + " on line " + currentLine + "; does not match " + opSym.token + " at line " + opSym.theLine ); errors++; } } /** * After the opening slash is seenm deal with next character. * If it is a comment starter, process it; otherwise putback * the next character if it is not a newline. */ private void processSlash( ) { if( nextChar( ) ) { if( ch == '*' ) { // Javadoc comment if( nextChar( ) && ch != '*' ) putBackChar( ); skipComment( SLASH_STAR ); } else if( ch == '/' ) skipComment( SLASH_SLASH ); else if( ch != '\n' ) putBackChar( ); } } /** * main routine for balanced symbol checker. * Slightly different from text. */ public static void main( String [ ] args ) { JavaAnalyzer p; if( args.length == 0 ) { p = new JavaAnalyzer( new PushbackReader( new InputStreamReader( System.in ) ) ); if( p.checkBalance( ) == 0 ) System.out.println( "No errors!" ); return; } for( int i = 0; i < args.length; i++ ) { try { FileReader f = new FileReader( args[ i ] ); System.out.println( args[ i ] + ": " ); p = new JavaAnalyzer( new PushbackReader( f ) ); if( p.checkBalance( ) == 0 ) System.out.println( " ...no errors!" ); f.close( ); } catch( IOException e ) { System.err.println( e + args[ i ] ); } } } }