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