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