import java.io.Reader; import java.io.StringReader; import java.io.IOException; /** * This class implements a simply one char lookahead scanner, with * performs the lexicalical analysis of the expression.<br> * The Scanner can be constructed using a String or a Stream (Reader), * to get the first/next token call <tt>nextToken</tt>. If <tt>nextToken</tt> * returns VECTOR or SCALAR, the value can be accessed via the public * member variable <tt>val</tt><br><br> * @see Scanner#nextToken * @see TestVektoren#test_Scanner */ public class Scanner { // token types public static final int EOF=-1; public static final int SCALAR=0; public static final int VECTOR=1; public static final int PLUS=2; public static final int MINUS=3; public static final int MULT=4; public static final int DIV=5; public static final int LPARAN=6; public static final int RPARAN=7; private static final String[] tokenNames = new String[] { "Scalar", "Vector", "+", "-", "*", "/", "(", ")" }; private Reader in=null; private int la=EOF; private int offset=0; private int tokenStart=0; // to check some restrictions private boolean nastyEprogRestrictions = false; private int[] backLog = new int[] { Scanner.EOF, Scanner.EOF, Scanner.EOF, Scanner.EOF }; private int nestingLevel=0; /** contains the actual value of <tt>nextToken</tt> returns SCALAR | VECTOR */ public Value val=Value.ZERO; public Scanner(String expr, boolean nastyEprogRestrictions) throws IOException { this( new StringReader( expr ), nastyEprogRestrictions ); } public Scanner(Reader in, boolean nastyEprogRestrictions) throws IOException { this.in = in; this.nastyEprogRestrictions = nastyEprogRestrictions; nextCh(); } /** * @return the start offset of the last token returned by <tt>nextToken</tt>, * can be used to construct error messages */ public int getTokenStart() { return tokenStart; } private void nextCh() throws IOException { offset += (la!=EOF)?1:0; la=in.read(); } private void match( char ch ) throws Exception { if ( la == ch ) nextCh(); else throw new ScannerException( (char)la, offset, ch ); } private double scanDigit() throws Exception { // 20 characters should be enough, otherwise StringBuffer will grow // automatical StringBuffer strValue = new StringBuffer( 20 ); int digits=0; if ( la == '+' || la == '-' ) { strValue.append( (char)la ); nextCh(); } while ( ('0' <= la) && ( la <= '9' ) ) { digits++; strValue.append( (char)la ); nextCh(); } if ( la == '.' ) { strValue.append( (char)la ); nextCh(); while ( ('0' <= la) && ( la <= '9' ) ) { digits++; strValue.append( (char)la ); nextCh(); } } if ( digits == 0 ) throw new NumberFormatException("a scalar must have at least 1 digit"); return Double.parseDouble( strValue.toString() ); } private void checkRange( double x ) throws NastyEprogException { if ( x < -100.0 || x > 100.0 ) throw new NastyEprogException("value not in range [-100.0,100.0] at offset " + tokenStart ); } /** * @return the next token */ public int nextToken() throws Exception { int iRet = EOF; tokenStart = offset; if ( la != EOF ) { if ( la == '+' ) { nextCh(); iRet = PLUS; } else if ( la == '-' ) { nextCh(); iRet = MINUS; } else if ( la == '*' ) { nextCh(); iRet = MULT; } /* to also allow DIV uncomment this block else if ( la == '/' ) { nextCh(); iRet = DIV; } */ else if ( la == '(' ) { nextCh(); iRet = LPARAN; nestingLevel++; } else if ( la == ')' ) { nextCh(); iRet = RPARAN; nestingLevel--; } else if ( ( ( '0' <= la ) && ( la <= '9' ) ) || la == '.' ) { double x = scanDigit(); iRet = SCALAR; if ( nastyEprogRestrictions ) { checkRange( x ); } val = new Value( x ); } else if ( la == '[' ) { nextCh(); double x = scanDigit(); match( ',' ); double y = scanDigit(); match( ',' ); double z = scanDigit(); match( ']' ); if ( nastyEprogRestrictions ) { checkRange( x ); checkRange( y ); checkRange( z ); } iRet = VECTOR; val = new Value( x, y, z ); } else throw new ScannerException( (char)la, offset ); } if ( nastyEprogRestrictions ) { // check if scalar is inbetween ( ) for( int i=0; i<3; i++ ) backLog[i] = backLog[i+1]; backLog[3] = iRet; if ( backLog[2] == SCALAR ) { if ( ! ( ( backLog[0] == LPARAN && ( backLog[1] == PLUS || backLog[1] == MINUS ) && backLog[3] == RPARAN ) || ( backLog[1] == LPARAN && backLog[3] == RPARAN ) ) ) throw new NastyEprogException("a scalar must be inbetween ( and ) at offset " + tokenStart ); } // checking the nestinglevel, according to the specification only // 1 level of paranteses is allowed (accept if it is a scalar, // with must be inbetween () if ( (nestingLevel>2) || ( nestingLevel>=2 && iRet == VECTOR ) ) throw new NastyEprogException("too many ( ), nestinglevel to high at offset " + tokenStart ); } return iRet; } /** * @return a human readable name for a given tokentype */ public static String tokenName( int token ) { if ( token == -1 ) return "EOF"; else if ( SCALAR <= token && token <= RPARAN ) return tokenNames[ token ]; else return "Unknown Token Type"; } }