Organizing import, refresh the license (zapata deleted cos.jar)
[mir.git] / source / mir / util / ParameterExpander.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License, 
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library 
23  * (or with modified versions of the above that use the same license as the above), 
24  * and distribute linked combinations including the two.  You must obey the 
25  * GNU General Public License in all respects for all of the code used other than 
26  * the above mentioned libraries.  If you modify this file, you may extend this 
27  * exception to your version of the file, but you are not obligated to do so.  
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mir.util;
31
32 import java.util.HashMap;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Vector;
37
38 import mir.generator.Generator;
39 import mir.generator.GeneratorExc;
40 import multex.Exc;
41
42 public class ParameterExpander {
43   final static String NODE_SEPARATOR = ".";
44   final static char STRING_ESCAPE_CHARACTER = '\\';
45
46   private static Object findNode(String aKey, Map aMap, List aParts, boolean aMakeIfNotPresent) throws Exception {
47     Iterator i;
48     String location = "";
49     Object node = aMap;
50     Object newNode;
51
52     i = aParts.iterator();
53
54     while (i.hasNext()) {
55       String part = (String) i.next();
56
57       if (!(node instanceof Map)) {
58         throw new Exception( "Can't expand key " + aKey + ": " + location + " is not a map" );
59       }
60
61       if (location.length()>0) {
62         location=location + NODE_SEPARATOR;
63       }
64       location = location + part;
65
66       newNode = ((Map) node).get(part);
67
68       if (newNode == null)
69         if (aMakeIfNotPresent) {
70           newNode = new HashMap();
71           ((Map) node).put(part, newNode);
72         }
73         else
74           throw new ParameterExpanderExc( "Can't expand key {1}: {2} does not exist", new Object[]{aKey,location} );
75
76       node = newNode;
77     }
78
79     return node;
80   }
81
82   public static Object findValueForKey(Map aMap, String aKey) throws Exception {
83     Object node;
84     List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
85
86     node = findNode(aKey, aMap, parts, false);
87
88     return node;
89   }
90
91   public static String findStringForKey(Map aMap, String aKey) throws Exception {
92     Object expandedValue = findValueForKey(aMap, aKey);
93
94     if (!(expandedValue instanceof String))
95       throw new ParameterExpanderExc( "Value of key is not a string but a {1}", new Object[]{expandedValue.getClass().getName()} );
96
97     return (String) expandedValue;
98   }
99
100   public static void setValueForKey(Map aMap, String aKey, Object aValue) throws Exception {
101     List parts = StringRoutines.splitString(aKey, NODE_SEPARATOR);
102
103     String key = (String) parts.get(parts.size()-1);
104     parts.remove(parts.size()-1);
105
106     Object node=findNode(aKey, aMap, parts, true);
107
108     if (node instanceof Map) {
109       ((Map) node).put(key, aValue);
110     }
111     else
112       throw new ParameterExpanderExc( "Can't set key {1}: not inside a Map", new Object[]{aKey} );
113   }
114
115   public static String expandExpression(Map aMap, String anExpression) throws Exception {
116     int previousPosition = 0;
117     int position;
118     int endOfExpressionPosition;
119     StringBuffer result = new StringBuffer();
120
121     while ((position=anExpression.indexOf("$", previousPosition))>=0) {
122       result.append(anExpression.substring(previousPosition, position));
123
124       if (position>=anExpression.length()-1) {
125         result.append(anExpression.substring(position, anExpression.length()));
126         previousPosition=anExpression.length();
127       }
128       else
129       {
130         if (anExpression.charAt(position+1) == '{') {
131           endOfExpressionPosition=position+2;
132           while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != '}') {
133             if (anExpression.charAt(endOfExpressionPosition)=='\'' || anExpression.charAt(endOfExpressionPosition)=='"') {
134               char boundary = anExpression.charAt(endOfExpressionPosition);
135
136               endOfExpressionPosition++;
137               while (endOfExpressionPosition<anExpression.length() && anExpression.charAt(endOfExpressionPosition) != boundary) {
138                 if (anExpression.charAt(endOfExpressionPosition) == STRING_ESCAPE_CHARACTER)
139                   endOfExpressionPosition++;
140                 endOfExpressionPosition++;
141               }
142               if (endOfExpressionPosition>=anExpression.length()) {
143                 throw new ParameterExpanderExc("Unterminated string in {1}",new Object[]{anExpression});
144               }
145             }
146             endOfExpressionPosition++;
147           }
148           if (endOfExpressionPosition<anExpression.length()) {
149             result.append(evaluateStringExpression(aMap, anExpression.substring(position+2, endOfExpressionPosition)));
150             previousPosition=endOfExpressionPosition+1;
151           }
152           else {
153             throw new ParameterExpanderExc("Missing } in {1}",new Object[]{anExpression});
154           }
155         }
156         else
157         {
158           previousPosition=position+2;
159           result.append(anExpression.charAt(position+1));
160         }
161       }
162     }
163     result.append(anExpression.substring(previousPosition, anExpression.length()));
164
165     return result.toString();
166   }
167
168   public static boolean evaluateBooleanExpression(Map aMap, String anExpression) throws Exception {
169     Parser parser = new Parser(anExpression, aMap);
170
171     return parser.parseBoolean();
172   }
173
174   public static String evaluateStringExpression(Map aMap, String anExpression) throws Exception {
175     Parser parser = new Parser(anExpression, aMap);
176
177     return parser.parseString();
178   }
179
180   public static int evaluateIntegerExpressionWithDefault(Map aMap, String anExpression, int aDefault) throws Exception {
181     if (anExpression == null || anExpression.trim().equals(""))
182       return aDefault;
183     else
184       return evaluateIntegerExpression(aMap, anExpression);
185   }
186
187   public static int evaluateIntegerExpression(Map aMap, String anExpression) throws Exception {
188     Parser parser = new Parser(anExpression, aMap);
189
190     return parser.parseInteger();
191   }
192
193   public static Object evaluateExpression(Map aMap, String anExpression) throws Exception {
194     Parser parser = new Parser(anExpression, aMap);
195
196     return parser.parseWhole();
197   }
198
199   private static class Reader {
200     private String data;
201     private int position;
202
203     public Reader(String aData) {
204       data = aData;
205       position=0;
206     }
207
208     public Character peek() {
209       if (position<data.length()) {
210         return (new Character(data.charAt(position)));
211       }
212
213       return null;
214     }
215
216     public boolean hasNext() {
217       return peek()!=null;
218     }
219
220     public Character getNext() {
221       Character result = peek();
222
223       if (result!=null)
224         position++;
225
226       return result;
227     }
228
229     public String getPositionString() {
230       return data.substring(0, position) + "<__>" + data.substring(position) ;
231     }
232   }
233
234   private static abstract class Token {
235   }
236
237   public static abstract class PunctuationToken extends Token { public PunctuationToken() { }; }
238     private static class LeftSquareBraceToken extends PunctuationToken {};
239     private static class RightSquareBraceToken extends PunctuationToken {};
240     private static class EqualsToken extends PunctuationToken {};
241     private static class EqualsNotToken extends PunctuationToken {};
242     private static class NOTToken extends PunctuationToken {};
243     private static class LeftParenthesisToken extends PunctuationToken {};
244     private static class RightParenthesisToken extends PunctuationToken {};
245     private static class CommaToken extends PunctuationToken {};
246     private static class PeriodToken extends PunctuationToken {};
247     private static class PlusToken extends PunctuationToken {};
248     private static class TimesToken extends PunctuationToken {};
249     private static class DivideToken extends PunctuationToken {};
250     private static class MinusToken extends PunctuationToken {};
251     private static class ConcatenateToken extends PunctuationToken {};
252     private static class LessThanOrEqualsToken extends PunctuationToken {};
253     private static class GreaterThanOrEqualsToken extends PunctuationToken {};
254     private static class LessThanToken extends PunctuationToken {};
255     private static class GreaterThanToken extends PunctuationToken {};
256
257
258   private static class IdentifierToken extends Token {
259     private String name;
260
261     public IdentifierToken(String aName) {
262       name = aName;
263     }
264
265     public String getName() {
266       return name;
267     }
268
269   }
270
271   private static class LiteralToken extends Token {
272     private Object value;
273
274     public LiteralToken(Object aValue) {
275       value = aValue;
276     }
277
278     public Object getValue() {
279       return value;
280     }
281   }
282
283   private static class Scanner {
284     private Reader reader;
285     private Token nextToken;
286     private String positionString;
287
288     public Scanner(Reader aReader) {
289       reader = aReader;
290       skipWhitespace();
291       positionString = reader.getPositionString();
292     }
293
294     public Token scanStringLiteral() {
295       StringBuffer result = new StringBuffer();
296       Character delimiter;
297
298       delimiter = reader.getNext();
299
300       while (reader.hasNext() && !reader.peek().equals(delimiter)) {
301         if (reader.peek().charValue()==STRING_ESCAPE_CHARACTER) {
302           reader.getNext();
303           if (reader.hasNext())
304             result.append(reader.getNext());
305         }
306         else {
307           result.append(reader.getNext());
308         }
309       }
310
311       if (!reader.hasNext())
312         throw new RuntimeException("unterminated string");
313       else
314         reader.getNext();
315
316       return new LiteralToken(result.toString());
317     }
318
319     public String getPositionString() {
320       return positionString;
321     }
322
323     private Token scanNumber() {
324       StringBuffer result = new StringBuffer();
325       result.append(reader.getNext());
326
327       while (reader.hasNext() && isNumberRest(reader.peek().charValue())) {
328         result.append(reader.getNext());
329       }
330
331       try {
332         return new LiteralToken(new Integer(Integer.parseInt(result.toString())));
333       }
334       catch (NumberFormatException e) {
335         throw new RuntimeException("Invalid number: " + e.getMessage());
336       }
337     }
338
339     private Token scanIdentifierKeyword() {
340       StringBuffer result = new StringBuffer();
341       result.append(reader.getNext());
342
343       while (reader.hasNext() && isIdentifierRest(reader.peek().charValue())) {
344         result.append(reader.getNext());
345       }
346
347       return new IdentifierToken(result.toString());
348     }
349
350     private Token scanPunctuation() {
351       Character c;
352
353       c = reader.getNext();
354
355       switch(c.charValue()) {
356         case '[': return new LeftSquareBraceToken();
357         case ']': return new RightSquareBraceToken();
358         case '=':
359           if (reader.hasNext() && reader.peek().charValue() == '=') {
360             reader.getNext();
361             return new EqualsToken();
362           }
363           else {
364             throw new RuntimeException("Unknown character: '='");
365           }
366
367         case '!':
368           if (reader.hasNext() && reader.peek().charValue() == '=') {
369             reader.getNext();
370             return new EqualsNotToken();
371           }
372           else {
373             return new NOTToken();
374           }
375
376         case '(': return new LeftParenthesisToken ();
377
378         case ')': return new RightParenthesisToken ();
379         case ',': return new CommaToken ();
380         case '.': return new PeriodToken ();
381         case '+':
382           if (reader.hasNext() && reader.peek().charValue() == '+') {
383             reader.getNext();
384             return new ConcatenateToken();
385           }
386           else {
387             return new PlusToken ();
388           }
389         case '*': return new TimesToken ();
390         case '/': return new DivideToken ();
391         case '-': return new MinusToken ();
392         case '<':
393           if (reader.hasNext() && reader.peek().charValue() == '=') {
394             reader.getNext();
395             return new LessThanOrEqualsToken();
396           }
397           else {
398             return new LessThanToken();
399           }
400
401         case '>':
402           if (reader.hasNext() && reader.peek().charValue() == '=') {
403             reader.getNext();
404             return new GreaterThanOrEqualsToken();
405           }
406           else {
407             return new GreaterThanToken();
408           }
409         default:
410           throw new RuntimeException("Unexpected character: "+c);
411       }
412     }
413
414     public void skipWhitespace() {
415       while (reader.hasNext() && Character.isWhitespace(reader.peek().charValue()))
416         reader.getNext();
417     };
418
419     private boolean isIdentifierStart(char c) {
420       return Character.isLetter(c) || (c == '_');
421     }
422
423     private boolean isIdentifierRest(char c) {
424       return Character.isLetterOrDigit(c) || (c == '_');
425     }
426
427     private boolean isNumberStart(char c) {
428       return Character.isDigit(c);
429     }
430
431     private boolean isNumberRest(char c) {
432       return Character.isDigit(c);
433     }
434
435     public Token scanNext() {
436       Token result = null;
437
438       skipWhitespace();
439
440       if (reader.hasNext()) {
441         Character c = reader.peek();
442
443         switch(c.charValue()) {
444           case '\'':
445           case '"':
446             result = scanStringLiteral();
447             break;
448
449           default: {
450             if (isIdentifierStart(c.charValue())) {
451               result = scanIdentifierKeyword();
452             }
453             else if (isNumberStart(c.charValue())) {
454               result = scanNumber();
455             }
456             else
457               result = scanPunctuation();
458           }
459         }
460       }
461
462       skipWhitespace();
463
464       return result;
465     }
466
467     public Token scan() {
468       Token result = peek();
469       nextToken = null;
470       positionString = reader.getPositionString();
471
472       return result;
473     }
474
475     public Token peek() {
476       if (nextToken==null) {
477         nextToken = scanNext();
478       }
479
480       return nextToken;
481     }
482
483     public boolean hasToken() {
484       return peek()!=null;
485     }
486   }
487
488   private static class Parser {
489     private Scanner scanner;
490     private Map valueMap;
491
492     public Parser(String anExpression, Map aValueMap) {
493       scanner = new Scanner(new Reader(anExpression));
494       valueMap = aValueMap;
495     }
496
497     public boolean parseBoolean() {
498       try {
499         return interpretAsBoolean(parseWhole());
500       }
501       catch (Throwable t) {
502         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
503       }
504     }
505
506     public int parseInteger() {
507       try {
508         return interpretAsInteger(parseWhole());
509       }
510       catch (Throwable t) {
511         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
512       }
513     }
514
515     public String parseString() {
516       try {
517         return interpretAsString(parseWhole());
518       }
519       catch (Throwable t) {
520         throw new RuntimeException("Parser error at '" + getLocation()+ "': "+t.getMessage());
521       }
522     }
523
524     private String getLocation() {
525       return scanner.getPositionString();
526     }
527
528     private Object parseWhole() {
529       Object result = parse();
530
531       if (scanner.hasToken()) {
532         throw new RuntimeException("Operator expected");
533       }
534
535       return result;
536     }
537
538     private Object parse() {
539       return parseUntil(MAX_OPERATOR_LEVEL);
540     }
541
542     private List parseList() {
543       Token token;
544       Object expression;
545       List result = new Vector();
546
547       token = scanner.scan();
548       if (!(token instanceof LeftParenthesisToken)) {
549         throw new RuntimeException("( expected");
550       }
551
552       if (scanner.peek() instanceof RightParenthesisToken) {
553         scanner.scan();
554         return result;
555       }
556
557       do {
558         expression = parse();
559
560         if (expression==null) {
561           throw new RuntimeException("expression expected");
562         }
563
564         result.add(expression);
565
566         token = scanner.scan();
567       }
568       while (token instanceof CommaToken);
569
570       if (!(token instanceof RightParenthesisToken)) {
571         throw new RuntimeException(") or , expected");
572       }
573
574       return result;
575     }
576
577     private Object parseVariable() {
578       boolean done;
579       Token token;
580       Object currentValue = valueMap;
581       Object qualifier;
582       List parameters;
583
584       do {
585         token = scanner.peek();
586
587         if (token instanceof LeftSquareBraceToken) {
588           scanner.scan();
589           qualifier = parseUntil(MAX_OPERATOR_LEVEL);
590           token = scanner.scan();
591           if (!(token instanceof RightSquareBraceToken))
592             throw new RuntimeException("] expected");
593
594           if (currentValue instanceof Map) {
595             currentValue = ((Map) currentValue).get(qualifier);
596           }
597           else {
598             throw new RuntimeException("cannot reference into anything other than a map ('"+qualifier+"')");
599           }
600         }
601         else if (token instanceof IdentifierToken) {
602           scanner.scan();
603           qualifier = ((IdentifierToken) token).getName();
604
605           if (currentValue instanceof Map) {
606             currentValue = ((Map) currentValue).get(qualifier);
607           }
608           else {
609             throw new RuntimeException("cannot reference into anything other than a map ('"+qualifier+"')");
610           }
611         }
612         else if (token instanceof LeftParenthesisToken) {
613           if (currentValue instanceof Generator.GeneratorFunction) {
614             parameters = parseList();
615             try {
616               currentValue = ((Generator.GeneratorFunction) currentValue).perform(parameters);
617             }
618             catch (GeneratorExc t) {
619               throw new RuntimeException(t.getMessage());
620             }
621           }
622           else
623             throw new RuntimeException("not a function");
624         }
625         else
626           throw new RuntimeException("fieldname or [ expected");
627
628         if (scanner.peek() instanceof PeriodToken ||
629             scanner.peek() instanceof LeftSquareBraceToken ||
630             scanner.peek() instanceof LeftParenthesisToken) {
631           done = false;
632
633           if (scanner.peek() instanceof PeriodToken)
634             scanner.scan();
635         }
636         else
637           done = true;
638       } while (!done);
639
640       return currentValue;
641     }
642
643
644     private Object parseUntil(int aMaxOperatorLevel) {
645       Token token = scanner.peek();
646       Object value;
647
648       if (token instanceof LeftParenthesisToken) {
649         scanner.scan();
650         value = parse();
651         token = scanner.peek();
652         if (!(token instanceof RightParenthesisToken))
653           throw new RuntimeException(") expected");
654         scanner.scan();
655       }
656       else if (isUnaryOperator(token)) {
657         scanner.scan();
658         value = parseUntil(unaryOperatorLevel(token));
659         value = expandOperatorExpression(token, value);
660       }
661       else if (token instanceof IdentifierToken || token instanceof LeftSquareBraceToken) {
662         value = parseVariable();
663       }
664       else if (token instanceof LiteralToken) {
665         scanner.scan();
666         value = ((LiteralToken) token).getValue();
667       }
668       else
669         throw new RuntimeException("Expression expected");
670
671       token = scanner.peek();
672
673       while (isBinaryOperator(token) && binaryOperatorLevel(token)<aMaxOperatorLevel) {
674         Object value2;
675         scanner.scan();
676
677         if (isINOperator(token)) {
678           value2 = parseList();
679         }
680         else {
681           value2 = parseUntil(binaryOperatorLevel(token));
682         }
683
684         value = expandOperatorExpression(token, value, value2);
685
686         token = scanner.peek();
687       }
688
689       return value;
690     }
691
692     private static final int MAX_OPERATOR_LEVEL = 1000;                //
693     private static final int LOGICAL_OPERATOR_LEVEL = 5;               // && || !
694     private static final int COMPARISON_OPERATOR_LEVEL = 4;            // == <= >= in < >
695     private static final int ADDITION_OPERATOR_LEVEL = 3;              // + - &
696     private static final int MULTIPLICATION_OPERATOR_LEVEL = 2;        // * /
697
698     private int unaryOperatorLevel(Token aToken) {
699       if (aToken instanceof NOTToken)
700         return LOGICAL_OPERATOR_LEVEL;
701       else if (aToken instanceof MinusToken)
702         return ADDITION_OPERATOR_LEVEL;
703
704       throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
705     }
706
707     private boolean isUnaryOperator(Token aToken) {
708       return
709           ((aToken instanceof NOTToken) ||
710            (aToken instanceof MinusToken));
711     }
712
713     private int binaryOperatorLevel(Token aToken) {
714       if (isANDOperator(aToken) ||
715           isOROperator(aToken))
716         return LOGICAL_OPERATOR_LEVEL;
717
718       if ((aToken instanceof EqualsToken) ||
719           (aToken instanceof EqualsNotToken) ||
720           (aToken instanceof LessThanOrEqualsToken) ||
721           (aToken instanceof LessThanToken) ||
722           (aToken instanceof GreaterThanOrEqualsToken) ||
723           (aToken instanceof GreaterThanToken) ||
724           isINOperator(aToken))
725         return COMPARISON_OPERATOR_LEVEL;
726
727       if ((aToken instanceof PlusToken) ||
728           (aToken instanceof ConcatenateToken) ||
729           (aToken instanceof MinusToken))
730         return ADDITION_OPERATOR_LEVEL;
731
732       if ((aToken instanceof TimesToken) ||
733           (aToken instanceof DivideToken))
734         return MULTIPLICATION_OPERATOR_LEVEL;
735
736       throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
737     }
738
739     private boolean isINOperator(Token aToken) {
740       return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("in"));
741     }
742
743     private boolean isANDOperator(Token aToken) {
744       return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("and"));
745     }
746
747     private boolean isOROperator(Token aToken) {
748       return (aToken instanceof IdentifierToken && ((IdentifierToken) aToken).getName().equals("or"));
749     }
750
751     private boolean isBinaryOperator(Token aToken) {
752       return
753            (aToken instanceof EqualsToken) ||
754            (aToken instanceof EqualsNotToken) ||
755            (aToken instanceof PlusToken) ||
756            (aToken instanceof TimesToken) ||
757            (aToken instanceof DivideToken) ||
758            (aToken instanceof MinusToken) ||
759            (aToken instanceof ConcatenateToken) ||
760            (aToken instanceof LessThanOrEqualsToken) ||
761            (aToken instanceof LessThanToken) ||
762            (aToken instanceof GreaterThanOrEqualsToken) ||
763            (aToken instanceof GreaterThanToken) ||
764            isINOperator(aToken) ||
765            isOROperator(aToken) ||
766            isANDOperator(aToken);
767     }
768
769     private boolean interpretAsBoolean(Object aValue) {
770       if (aValue instanceof Boolean)
771         return ((Boolean) aValue).booleanValue();
772
773       return aValue!=null;
774     }
775
776     private int interpretAsInteger(Object aValue) {
777       if (aValue instanceof Integer)
778         return ((Integer) aValue).intValue();
779
780       if (aValue instanceof String) {
781         try {
782           return Integer.parseInt((String) aValue);
783         }
784         catch (Throwable t) {
785         }
786       }
787
788       throw new RuntimeException("Not an integer");
789     }
790
791     private String interpretAsString(Object aValue) {
792       if (aValue instanceof String)
793         return (String) aValue;
794       if (aValue instanceof Integer)
795         return ((Integer) aValue).toString();
796
797       throw new RuntimeException("Not a string");
798     }
799
800     private Object expandOperatorExpression(Token aToken, Object aValue) {
801       if (aToken instanceof NOTToken)
802         return new Boolean(!interpretAsBoolean(aValue));
803       else if (aToken instanceof MinusToken)
804         return new Integer(-interpretAsInteger(aValue));
805
806       throw new RuntimeException("Internal error: unknown unary operator: " + aToken.getClass().getName());
807     }
808
809     private boolean areEqual(Object aValue1, Object aValue2) {
810       if (aValue1==null || aValue2==null)
811         return (aValue1==null) && (aValue2==null);
812       else
813         return aValue1.equals(aValue2);
814     }
815
816     private Object expandOperatorExpression(Token aToken, Object aValue1, Object aValue2) {
817       if (isINOperator(aToken)) {
818         if (!(aValue2 instanceof List)) {
819           throw new RuntimeException("Internal error: List expected");
820         }
821
822         Iterator i = ((List) aValue2).iterator();
823
824         while (i.hasNext()) {
825           if (areEqual(aValue1, i.next()))
826             return Boolean.TRUE;
827         }
828
829         return Boolean.FALSE;
830       }
831
832       if (isANDOperator(aToken))
833         return new Boolean(interpretAsBoolean(aValue1) && interpretAsBoolean(aValue2));
834       if (isOROperator(aToken))
835         return new Boolean(interpretAsBoolean(aValue1) || interpretAsBoolean(aValue2));
836       if (aToken instanceof EqualsToken) {
837         return new Boolean(areEqual(aValue1, aValue2));
838       }
839       if (aToken instanceof EqualsNotToken)
840         return new Boolean(!areEqual(aValue1, aValue2));
841       if (aToken instanceof PlusToken)
842         return new Integer(interpretAsInteger(aValue1) + interpretAsInteger(aValue2));
843       if (aToken instanceof TimesToken)
844         return new Integer(interpretAsInteger(aValue1) * interpretAsInteger(aValue2));
845       if (aToken instanceof DivideToken)
846         return new Integer(interpretAsInteger(aValue1) / interpretAsInteger(aValue2));
847       if (aToken instanceof MinusToken)
848         return new Integer(interpretAsInteger(aValue1) - interpretAsInteger(aValue2));
849
850       if (aToken instanceof ConcatenateToken)
851         return interpretAsString(aValue1) + interpretAsString(aValue2);
852
853       if (aToken instanceof LessThanOrEqualsToken)
854         return new Boolean(interpretAsInteger(aValue1) <= interpretAsInteger(aValue2));
855       if (aToken instanceof LessThanToken)
856         return new Boolean(interpretAsInteger(aValue1) < interpretAsInteger(aValue2));
857       if (aToken instanceof GreaterThanOrEqualsToken)
858         return new Boolean(interpretAsInteger(aValue1) >= interpretAsInteger(aValue2));
859       if (aToken instanceof GreaterThanToken)
860         return new Boolean(interpretAsInteger(aValue1) > interpretAsInteger(aValue2));
861
862       throw new RuntimeException("Internal error: unknown binary operator: " + aToken.getClass().getName());
863     }
864   }
865
866   public static class ParameterExpanderExc extends Exc {
867     public ParameterExpanderExc(String msg, Object[] objects) {
868       super(msg, objects);
869     }
870   }
871 }