package aima.util; import java.io.StreamTokenizer; import java.io.Reader; import java.io.StringReader; import java.text.NumberFormat; import java.text.ParsePosition; /** * The Expression class provides static methods to facilitate JAVA * programming in terms of LISP-like atomic and dotted-pair expressions. * While the Expression class (currently) extends the * DottedPair class, this is done as a mere convenience so that * one can do the nicer looking: *
 *    Expression expr    = new Expression("a",new Integer(5));
 *    String     aString = (String) Expression.first(expr);
 * 
* rather than: *
 *    DottedPair dp      = new DottedPair("a",new Integer(5));
 *    String     aString = (String) Expression.first(dp);
 * 
* The idea here is that the Expression class, with its * associated static methods creates the illusion that it can * manipulate atomic as well as dotted pair objects, without accentuating * the fact that non-atomic objects are actually implemented in terms * of the DottedPair class. * * Other than the constructor method, all the other methods are * static, to conceptually keep separate the generic * atom/dotted-pair manipulation methods from the actual dotted-pair * implementation in the DottedPair class. An * atom in this implementation is any JAVA Object * that is neither an instance of the DottedPair class * nor is the special, unique (nil-like) object used to terminate * DottedPair's and to represent the empty list. * * The static methods of this class expect that run-time exceptions * will be thrown if they are called inappropriately. For example: *
 *    Integer    atom1  = new Integer(1);
 *    Integer    atom2  = new Integer(2);
 *    Expression expr12 = new Expression(atom1,atom2);
 *    Object     good   = Expression.first(expr12); // OK
 *    Object     bad    = Expression.first(atom1);  // Runtime error
 * 
* The last statement will throw an error because you cannot take the * first element of an atom. Currently this error is a JAVA mis-casting * runtime error, but perhaps in the future we may define an * Expression specific error to throw. * * @author Michael S. Braverman * @see DottedPair */ public class Expression extends DottedPair { /** * Creates a new compound (dotted) expresssion. * * @param car the Object to go to the left of the "dot" * @param cdr the Object to go to the right of the "dot" * * @see DottedPair */ public Expression (Object car, Object cdr) { super(car,cdr); } /** * Invokes the DottedPair reuse method, * assuming that compObj is a compound DottedPair object. * * @return the result of DottedPair.reuse * @param car see first parameter of DottedPair.reuse * @param cdr see second parameter of DottedPair.reuse * @param compObj the object on which to invoke DottedPair.reuse(car,cdr) * @exception ClassCastException Thrown at runtime if * compObj is not a compound DottedPair * object. * @see DottedPair#reuse */ static public DottedPair reuse (Object car, Object cdr, Object compObj) throws ClassCastException { return ((DottedPair) compObj).reuse(car,cdr); } /** * Tells whether or not the given object is the unique "NULL" object * that represents the empty compound expression (which is used to * terminate compound expressions that are true lists). * * @return true, iff obj is the NULL object. * @param obj Object to test. * @see DottedPair#getNull */ static public boolean isNull(Object obj) { return (obj == getNull()); } /** * Tells whether or not the given object is considered an atomic * expression. Analogous to the atom predicate in * LISP. * * @return true, iff obj is neither a dotted compound * expression, nor the special, unique NULL (empty) compound object. * @param obj Object to test. * @see DottedPair#isAtomic * @see DottedPair#getNull */ static public boolean isAtomic(Object obj) { // Note: we only cast when it is safe to do so return (!(obj instanceof DottedPair) || ((DottedPair) obj).isAtomic()); } /** * Tells whether or not the given object is considered a true compound * expression. Analogous to the consp predicate in * LISP. * * @return true, iff obj is a dotted compound but * not the special empty NULL compound expression. * @param obj Object to test. * @see DottedPair#isTrueCompound * @see DottedPair#getNull * @see #isCompound */ static public boolean isTrueCompound(Object obj) { return ((obj instanceof DottedPair) && ((DottedPair) obj).isTrueCompound()); } /** * Tells whether or not the given object is considered a compound * expression. Analogous to the listp predicate in * LISP. * * @return true, iff obj is a compound expression, * including the special empty NULL compound expression. * @param obj Object to test. * @see DottedPair */ static public boolean isCompound(Object obj) { return (obj instanceof DottedPair); } /** * Tells whether or not the given objects are equal, * analogous to the equal predicate in * LISP. * * @return true, iff obj1 and obj2 * are equal in the LISP equal sense. * @param obj1 first Object to compare. * @param obj2 second Object to compare. * @see DottedPair#equals */ static public boolean equals(Object obj1, Object obj2) { if (isAtomic(obj1) || isAtomic(obj2)) { return obj1.equals(obj2); } else { DottedPair obj1DP = (DottedPair) obj1; DottedPair obj2DP = (DottedPair) obj2; return (obj1DP.car().equals(obj2DP.car()) && obj1DP.cdr().equals(obj2DP.cdr())); } } /** * Tells whether or not the given objects are equal, * analogous to the eql predicate in * LISP. * * @return true, iff obj1 and obj2 * are equal in the LISP eql sense. * @param obj1 first Object to compare. * @param obj2 second Object to compare. * @see DottedPair#eql */ static public boolean eql(Object obj1, Object obj2) { if (isAtomic(obj1) && isAtomic(obj2)) { return obj1.equals(obj2); } else { return (obj1 == obj2); } } /** * Invokes the DottedPair assoc method, * assuming that assocList is a compound DottedPair object, * in the form of an association list. * *

* Note: Should one day implement a version of this code that takes * an arbitrary predicate, rather than assuming eql. * Similarly, should possibly take a function that directs the * search to more than just the first element of each pair. * * @return The pair whose first element is eql to the * given target, or the special Null object if no matching element * is found. * @param target the Object for which to search. * @param assocList the association list to scan. * @exception ClassCastException Thrown at runtime if * assocList is not a compound DottedPair * object. * @see DottedPair#assoc * @see #eql */ static public DottedPair assoc(Object target, Object assocList) throws ClassCastException { return ((DottedPair) assocList).assoc(target); } /** * Invokes the DottedPair length method, * assuming that argument list is a compound * DottedPair object, and returns * the number of top-level elements of this list. * * @return The number of top-level elements of this list. * @param listObj a true (Null terminated) list * @exception ClassCastException Thrown at runtime if this * is not a DottedPair that is also a true list * (ends in a non-Null atomic Object). * @see DottedPair#getNull * @see DottedPair#length */ static public int length(Object listObj) throws ClassCastException { return ((DottedPair) listObj).length(); } /** * Invokes the DottedPair member method, * assuming that searchList is a compound DottedPair object, * in the form of a true list. * *

* Note: Should one day implement a version of this code that takes * an arbitrary predicate, rather than assuming eql. * * @return The sublist of the given searchList whose first * element is eql to the given target. * @param target the Object for which to search. * @param searchList the list to search. * @exception ClassCastException Thrown at runtime if * searchList is not a compound DottedPair * object. * @see DottedPair#member * @see #eql */ static public DottedPair member(Object target, Object searchList) throws ClassCastException { return ((DottedPair) searchList).member(target); } /** * Invokes the DottedPair nthcdr method, * assuming that listObj is a compound DottedPair object, * in the form of a true list. * * @return The result of applying cdr the specified number * of times. * @param n the number of times to apply cdr * @param listObj the initial list. * @exception ClassCastException Thrown at runtime if * searchList is not a compound DottedPair * object. * @see DottedPair#nthcdr */ static public DottedPair nthcdr(int n, Object listObj) throws ClassCastException { return ((DottedPair) listObj).nthcdr(n); } /** * Invokes the DottedPair elt method, * assuming that listObj is a compound DottedPair object, * in the form of a true list. * * @return The element of the list found at the given (0-based) index. * @param listObj the list. * @param index the (0-based) index. * @exception ClassCastException Thrown at runtime if * listObj is not a compound DottedPair * object. * @exception RuntimeException Thrown if index is out * of bounds (negative or beyond the end of the list). * @see DottedPair#elt */ static public Object elt(Object listObj, int index) throws ClassCastException, RuntimeException { return ((DottedPair) listObj).elt(index); } /** * Invokes the DottedPair subseq method, * assuming that listObj is a compound DottedPair object. * * @return a top level copy of the indicated subset of the given list. * @exception ClassCastException Possibly thrown at runtime if this * is not a true list (ends in a non-Null atomic Object). * @exception RuntimeException Thrown if given indices are beyond * the bounds of the list. * @param listObj the list whose subset is to be copied. * @param start the (0-based) start index. * @param end the (0-based) end index. * @see DottedPair#subseq */ static public DottedPair subseq(Object listObj, int start, int end) throws ClassCastException, RuntimeException { return ((DottedPair) listObj).subseq(start,end); } /** * Invokes the DottedPair subseq method, * assuming that listObj is a compound DottedPair object. * * @return a top level copy of the indicated suffix of the given list. * @exception ClassCastException Possibly thrown at runtime if this * is not a true list (ends in a non-Null atomic Object). * @exception RuntimeException Thrown if given start index is beyond * the bounds of the list. * @param listObj the list whose suffix is to be copied. * @param start the (0-based) start index. * @see DottedPair#subseq */ static public DottedPair subseq(Object listObj, int start) throws ClassCastException, RuntimeException { return ((DottedPair) listObj).subseq(start); } /** * Invokes the DottedPair copySeq method, * assuming that listObj is a compound DottedPair object. * * @return a top level copy of the indicated list. * @exception ClassCastException Possibly thrown at runtime if this * is not a true list (ends in a non-Null atomic Object). * @see DottedPair#copySeq */ static public DottedPair copySeq(Object listObj) { return ((DottedPair) listObj).copySeq(); } /** * Invokes the DottedPair nreverse method, * assuming that listObj is a compound DottedPair object. * * @return the first DottedPair element of the (destructively) * reversed given list. * @exception ClassCastException Possibly thrown at runtime if this * is not a true list (ends in a non-Null atomic Object). * @param listObj the list to destructively reverse. * @see DottedPair#nreverse */ static public DottedPair nreverse(Object listObj) { return ((DottedPair) listObj).nreverse(); } /** * Invokes the DottedPair reverse method, * assuming that listObj is a compound DottedPair object. * * @return a reversed (non-destructive) copy of the given list. * @exception ClassCastException Possibly thrown at runtime if this * is not a true list (ends in a non-Null atomic Object). * @param listObj the list to reverse. * @see DottedPair#reverse */ static public DottedPair reverse(Object listObj) { return ((DottedPair) listObj).reverse(); } /** * Returns a new list that is the result of appending a (surface level) * copy of the first list argument to the second list argument. * *

* Note: As in LISP, the result contains a reference to the * second list argument passed (the passed argument is not itself copied). * * @return The appended result. * @param list1 the list to which to append. * @param list2 the list that is appended. * @exception ClassCastException Thrown at runtime if * list1 and list2 are not both * compound DottedPair objects. Additionally, list1 * had better be a true list or the same exception will be thrown when * the internal DottedPair.append method reaches the atomic * last element of list1. */ static public DottedPair append(Object list1, Object list2) throws ClassCastException { return ((DottedPair) list1).append((DottedPair) list2); } /** * Invokes the DottedPair car method, * assuming that compObj is a compound DottedPair object. * * @return the result of DottedPair.car (the first element of the pair) * @param compObj the object on which to invoke DottedPair.car() * @exception ClassCastException Thrown at runtime if * compObj is not a compound DottedPair * object. * @see DottedPair#car */ static public Object car(Object compObj) throws ClassCastException { return ((DottedPair) compObj).car(); } /** * An alias for the car method. * * @return the result of DottedPair.car (the first element of the pair) * @param compObj the object on which to invoke DottedPair.car() * @exception ClassCastException Thrown at runtime if * compObj is not a compound DottedPair * object. * @see #car */ static public Object first(Object compObj) throws ClassCastException { return car(compObj); } /** * Invokes the DottedPair cdr method, * assuming that compObj is a compound DottedPair object. * * @return the result of DottedPair.cdr (the right element of the pair) * @param compObj the object on which to invoke DottedPair.cdr() * @exception ClassCastException Thrown at runtime if * compObj is not a compound DottedPair * object. * @see DottedPair#cdr */ static public Object cdr(Object compObj) throws ClassCastException { return ((DottedPair) compObj).cdr(); } /** * An alias for the cdr method. * * @return the result of DottedPair.cdr (the right element of the pair) * @param compObj the object on which to invoke DottedPair.cdr() * @exception ClassCastException Thrown at runtime if * compObj is not a compound DottedPair * object. * @see #cdr */ static public Object rest(Object compObj) throws ClassCastException { return cdr(compObj); } /** * Reads and parses the next atomic or compound expression from the * given Reader stream. * * @return An object consistent with the description in the stream. * @param reader The stream from which to read. * @exception Exception An Exception will be thrown if a parsing error occurs. * @see java.io.Reader */ static public Object parse(Reader reader) throws Exception { StreamTokenizer tokenizer = new StreamTokenizer(reader); lexDottedPairSyntax(tokenizer); return parse(tokenizer); } /** * Reads and parses the first atomic or compound expression from the * given String. * * @return An object consistent with the description in str. * @param str The String to parse. * @exception Exception An Exception will be thrown if a parsing error occurs. */ static public Object parse(String str) throws Exception { return parse(new StringReader(str)); } /** * Reads and parses the next atomic or compound expression from the * given StreamTokenizer stream. * * @return An object consistent with the description in the stream. * @param tokenizer The token stream from which to read. * @exception Exception An Exception will be thrown if a parsing error * occurs. * @see java.io.StreamTokenizer */ private static Object parse(StreamTokenizer tokenizer) throws Exception { int tokenType = tokenizer.nextToken(); switch (tokenType) { case StreamTokenizer.TT_WORD: if (isDot(tokenizer)) { throw new Exception(". seen, but unexpected"); } else { String atom = tokenizer.sval; if ("-+.0123456789".indexOf(atom.charAt(0)) == -1) { return atom; } else { return parseNumber(atom); } } case '(': return parseDottedPair(tokenizer); case '"': case '|': return tokenizer.sval; default: throw new Exception("Unexpected token "+((char) tokenType)); } } /** * Converts the last-read token of the given StreamTokenizer * to a String representation. * * @return the String representation of the last-read * token. * @param tokenizer The tokenizer containing a read token. * @see java.io.StreamTokenizer */ private static String tokenToString(StreamTokenizer tokenizer) { switch (tokenizer.ttype) { case StreamTokenizer.TT_EOF: return "*END OF STREAM*"; case StreamTokenizer.TT_EOL: return "\n"; case StreamTokenizer.TT_NUMBER: return String.valueOf(tokenizer.nval); case StreamTokenizer.TT_WORD: return tokenizer.sval; default: return String.valueOf((char) tokenizer.ttype); } } /** * Initializes the syntax table of a StreamTokenizer * to read LISP-like atoms and compound expressions. * * @param tokenizer The tokenizer to initialize. * @see java.io.StreamTokenizer */ private static void lexDottedPairSyntax(StreamTokenizer tokenizer) { tokenizer.resetSyntax(); tokenizer.wordChars('a','z'); tokenizer.wordChars('A','Z'); tokenizer.wordChars('0','9'); tokenizer.whitespaceChars(0,' '); tokenizer.quoteChar('"'); tokenizer.quoteChar('|'); String constituents = "!$%&*.+-/:<=>?@[]^_{}~"; for (int i=0;iDottedPair. * * @param tokenizer The tokenizer from which to parse. * @return A DottedPair consistent with the contents * of the stream of tokens presented by the tokenizer. * @exception Exception A parsing exception may possibly be thrown from * called routines. * @see java.io.StreamTokenizer * @see DottedPair */ private static Object parseDottedPair(StreamTokenizer tokenizer) throws Exception { int tokenType = tokenizer.nextToken(); if (tokenType == ')') { return getNull(); } else { tokenizer.pushBack(); Object car = parse(tokenizer); Object cdr = parseRestDottedPair(tokenizer); return new Expression(car,cdr); } } /** * Parses the a number from the given String token. * Depending on the format of the number in the String, either a * Long or Double Java Number, * as appropriate, will be returned as the result of the parse. * This method is needed because the built in JAVA methods for * parsing numbers don't parse scientific notation. This method * parses numbers of the form: *

   *   [+-]dddd[.dddd][[eE][+-]dddd]
   * 
* @param token The token to parse. * @return A Number consistent in type with the * contents of the token. * @exception Exception A parsing exception may be thrown if the * token does not represent a well-formatted number. * @see java.text.NumberFormat */ private static Number parseNumber(String token) throws Exception { NumberFormat nf = NumberFormat.getInstance(); int tokenLength = token.length(); // We allow a leading +, so set parse position after it, if present: int parseIndex = (token.charAt(0)=='+'?1:0); ParsePosition pp = new ParsePosition(parseIndex); Number numObj = nf.parse(token,pp); parseIndex = pp.getIndex(); if (parseIndex == tokenLength) { return numObj; } else if ("eE".indexOf(token.charAt(parseIndex)) == -1) { throw new Exception("invalid numeric-looking object '"+token+"'"); } else { // Skip over the 'e' or 'E' parseIndex++; // Further skip over the optional leading '+', if present; if ((parseIndex < tokenLength) && (token.charAt(parseIndex) == '+')) parseIndex++; if (parseIndex >= tokenLength) { throw new Exception("invalid exponent in numeric-looking object '" + token+"'"); } else { pp.setIndex(parseIndex); nf.setParseIntegerOnly(true); Number expObj = nf.parse(token,pp); parseIndex = pp.getIndex(); if (parseIndex != tokenLength) { throw new Exception("invalid exponent in numeric-looking object '"+ token+"'"); } else { // For better numeric accuracy, find 10^abs(exponent) and then // divide by that value if exponent is negative (rather than // multiplying by the reciprocal 10^-abs(exponent) which can // lead to round off error). double num = numObj.doubleValue(); double exponentFactor = Math.pow(10.0,Math.abs(expObj.doubleValue())); if (expObj.doubleValue() >= 0) { return new Double(num*exponentFactor); } else { return new Double(num/exponentFactor); } } } } } /** * Tests out the functionality of this class. * @param args Ignored. */ public static void main(String args[]) { String ParseTests[] = {"(a b c)", "(a |b \\nc| d)", "(a \"b c\" d)", "(a (b . c) d)", "(a .)", "(a . b c)", "(a b.cdef c)", "(a 10 1.5 c)", "(a 10 1.5a c)", "(a 10 1.5E3 c)", "(a 10 .5E3 c)", "(a 10 -.5E3 c)", "(a 10 +.5E3 c)", "(a 10 1.5E3.5 c)", "(a 10 1.5E-1 c)", "(a 10 .15 c)", "(a 10 .15e+1 c)", "(a 10 15% c)", "(a . (b . (c . ())))", "(a .(b .(c .())))", "hello", ".", "12"}; System.out.println("*** Parsing Tests ***"); for (int i = 0; i < ParseTests.length; i++) { try { System.out.print(ParseTests[i]+" = "); System.out.println(parse(ParseTests[i])); } catch (Exception e) { System.out.println(e); } } String AssocTests[] = {"((a . 1) (b . 2) (c . 3) (d . 4))", "a", "((a . 1) (b . 2) (c . 3) (d . 4))", "c", "((a . 1) (b . 2) (c . 3) (d . 4))", "e", "((a . 1) (b . 2) ((c d) . 3) (e . 4))", "(c d)"}; System.out.println("*** Assoc Tests ***"); for (int i = 0; i < AssocTests.length/2; i++) { try { System.out.print("Expression.assoc("+AssocTests[2*i+1]+", "+ AssocTests[2*i]+") = "); System.out.println(Expression.assoc(parse(AssocTests[2*i+1]), parse(AssocTests[2*i]))); } catch (Exception e) { System.out.println(e); } } String AppendTests[] = {"(j k)", "(g (h . i))", "((d e) () f)", "((a b c))", "(generates . error)"}; System.out.println("*** Append Tests ***"); DottedPair accumulate = getNull(); for (int i = 0; i < AppendTests.length; i++) { try { System.out.print("Expression.append("+AppendTests[i]+", "+ accumulate+") = "); accumulate = Expression.append(parse(AppendTests[i]),accumulate); System.out.println(accumulate); } catch (Exception e) { System.out.println(e); } } System.out.println("*** Member Tests ***"); String MemberTests[] = {"a","b","c","d"}; try { DottedPair searchList = (DottedPair) Expression.parse("(a b no-c no-d)"); for (int i = 0; i < MemberTests.length; i++) { try { System.out.print("Expression.member("+MemberTests[i]+", "+ searchList+") = "); System.out.println(Expression.member(parse(MemberTests[i]), searchList)); } catch (Exception e) { System.out.println(e); } } } catch (Exception e) { System.out.println(e); } System.out.println("*** Elt Tests ***"); try { DottedPair eltList = (DottedPair) Expression.parse("(a b c d)"); for (int i = -1; i <= 4; i++) { try { System.out.print("Expression.elt("+eltList+", "+i+") = "); System.out.println(Expression.elt(eltList,i)); } catch (Exception e) { System.out.println(e); } } } catch (Exception e) { System.out.println(e); } System.out.println("*** Length Tests ***"); try { DottedPair list1 = (DottedPair) Expression.parse("(1 (a b) (c d) 4)"); DottedPair list2 = Expression.getNull(); System.out.println("Expression.length("+list1+") = " + Expression.length(list1)); System.out.println("Expression.length("+list2+") = " + Expression.length(list2)); } catch (Exception e) { System.out.println(e); } System.out.println("*** Nthcdr Tests ***"); try { DottedPair nthcdrList = (DottedPair) Expression.parse("(a b c d)"); for (int i = -1; i <= 4; i++) { try { System.out.print("Expression.nthcdr("+i+", "+nthcdrList+") = "); System.out.println(Expression.nthcdr(i,nthcdrList)); } catch (Exception e) { System.out.println(e); } } } catch (Exception e) { System.out.println(e); } System.out.println("*** Subseq(start,end) Tests ***"); try { DottedPair list = (DottedPair) Expression.parse("(a b c d)"); for (int i = -1; i <= 4; i++) { try { System.out.print("Expression.subseq("+list+","+i+",2) = "); System.out.println(Expression.subseq(list,i,2)); } catch (Exception e) { System.out.println(e); } } for (int i = -1; i <= 4; i++) { try { System.out.print("Expression.subseq("+list+","+2+","+i+") = "); System.out.println(Expression.subseq(list,2,i)); } catch (Exception e) { System.out.println(e); } } } catch (Exception e) { System.out.println(e); } System.out.println("*** Subseq(start) Tests ***"); try { DottedPair list = (DottedPair) Expression.parse("(a b c d)"); for (int i = -1; i <= 4; i++) { try { System.out.print("Expression.subseq("+list+","+i+") = "); System.out.println(Expression.subseq(list,i)); } catch (Exception e) { System.out.println(e); } } } catch (Exception e) { System.out.println(e); } System.out.println("*** Reverse Tests ***"); try { String tests[]={"(a b c d)", "(a b c . d)", // should throw an error "()", "(a (b . c) d)"}; for (int i = 0; i < tests.length; i++) { try { DottedPair list = (DottedPair) Expression.parse(tests[i]); System.out.print("Expression.reverse("+list+") = "); System.out.println(Expression.reverse(list)); } catch (Exception e) { System.out.println(e); } } } catch (Exception e) { System.out.println(e); } } }