View Javadoc
1   package com.randomnoun.common.jexl.eval;
2   
3   /* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
4    * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
5    */
6   
7   import java.math.BigDecimal;
8   import java.text.*; // for SimpleDateFormat & friends
9   import java.util.*;
10  
11  
12  
13  import org.apache.log4j.Logger;
14  
15  import com.randomnoun.common.Text;
16  import com.randomnoun.common.jexl.ast.*;
17  import com.randomnoun.common.jexl.visitor.*;
18  
19  
20  /**
21   * This class traverses (and evaluates) an expression AST graph. It
22   * traverses the AST in depth-first order (by extending the ObjectDepthFirst
23   * visitor class generated by jtb). This class implements the
24   * 'Visitor' design pattern.
25   *
26   * <p>Each visit() method in this class takes two parameters, the
27   * first being the node being visited, and the second contains
28   * an EvalContext object containing the variables and functions which can
29   * be referenced within the expression. The visitor (i.e. this class) is then
30   * passed to it's children nodes using the nodes' accept() method (generated
31   * by jtb).
32   *
33   * <p>Variables can be any wrappered primitive type (e.g. Long, Integer, ...),
34   * although mathematical evaluation (+, -, /, etc) are currently only
35   * implemented for Doubles and Longs.
36   *
37   * <p>This class can be considered thread-safe, since it holds no instance-wide state.
38   * (All methods that require access to a EvalContext have it passed in through a
39   * method parameter). i.e. many threads may use the same Evaluator instance to
40   * evaluate different expressions.
41   *
42   * <p>NB: In the javadocs below, "lhs" refers to the left-hand-side value of any binary
43   * operation, and "rhs" refers to the right-hand-side value; e.g. in "3 + 4",
44   * the lhs is "3", the rhs is "4" and the op is "+".
45   * 
46   * @author knoxg
47   */
48  public class Evaluator
49      extends GJDepthFirst<Object, EvalContext> {
50  
51      // We used to create a variable with this name before executing any function calls 
52  	// containing the Evaluator instance that is being used to evaluate the function. Not entirely sure why any more, so have commented it out.
53      // public static final String VAR_EVALUATOR = ".evaluator";
54  
55  
56      /** Logger instance for this class */
57      Logger logger = Logger.getLogger(Evaluator.class);
58  
59      // could probably use generics these days
60      // and I'm pretty sure these are in java.util.functions as well
61      
62      /** Binary operation interface; takes two parameters, returns a result. */
63      interface BinaryOp {
64          Object op(Object a, Object b);
65      }
66  
67      /** A binary operation on two parameters whose types are guaranteed to be Long. */
68      abstract class LongOp implements BinaryOp {
69          public Object op(Object a, Object b) {
70              long longA = ((Long) a).longValue();
71              long longB = (b == null ? 0 : ((Long) b).longValue());
72              return Long.valueOf(longOp(longA, longB));
73          }
74          abstract long longOp(long a, long b);
75      }
76  
77      /** A binary operation on two parameters whose types are guaranteed to be Double. */
78      abstract class DoubleOp implements BinaryOp {
79          public Object op(Object a, Object b) {
80              double doubleA = ((Double) a).doubleValue();
81              double doubleB = (b == null ? 0 : ((Double) b).doubleValue());
82              return Double.valueOf(doubleOp(doubleA, doubleB));
83          }
84          abstract double doubleOp(double a, double b);
85      }
86  
87      /** Coerce parameter 'b' into a compatible type of parameter 'a', to be used in a
88       *  binary math op. If 'b' can't be coerced into 'a', then throw an EvalException.
89       *
90       * @param a First binary op parameter (the type of which we are casting to)
91       * @param b Second binary op parameter (the value we are casting)
92       * @return The value of 'b', coerced to be the same type of value 'a'
93       */
94      public Object coerceType(Object a, Object b) {
95      	// don't coerce nulls
96      	if (a == null) { return null; }
97  
98          Class<? extends Object> clazz = a.getClass();
99          if (b == null || clazz.isInstance(b)) { return b; }
100 
101         if (a instanceof Date) {
102             if (b instanceof String) {
103                 // convert string to date
104                 DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
105                 try {
106                     return sdf.parse((String) b);
107                 } catch (java.text.ParseException pe) {
108                     throw new EvalException("Could not parse date '" + b + "': " + pe.getMessage());
109                 }
110             }
111         }
112 
113         if (a instanceof Double) {
114             if (b instanceof Number) {
115                 return Double.valueOf(((Number) b).doubleValue());
116             }
117         }
118 
119         if (a instanceof Long) {
120             if (b instanceof Number) {
121                 return Long.valueOf(((Number) b).longValue());
122             }
123         }
124 
125 		if (a instanceof BigDecimal) {
126 			if (b instanceof Number) {
127 				// not sure if this keeps enough precision, but good enough for testing 
128 				return new BigDecimal(((Number) b).toString());  
129 			}
130 		}
131 
132         throw new EvalException("cannot coerce " + b.getClass().getName() + " to " + clazz.getName());
133     }
134 
135     /** Binary math operation. Takes two parameters, the name of the op (only used in
136      * exception text), and BinaryOp classes for all supported types. (We only
137      * support math operations on Longs and Doubles at the moment, should be easy to
138      * add new types if required). If we were aiming for completeness, I'd implement
139      * Short, Int, Byte, Char, BigInteger and BigDecimal.
140      *
141      * <p>The rhs of the expression is coerced into the type of the lhs.
142      *
143      * @param a lhs parameter
144      * @param b rhs parameter
145      * @param op name of op
146      * @param longOp operation to perform if lhs is Long
147      * @param doubleOp operation to perform if lhs in Double
148      * @return the evaluated result of the op
149      */
150     public Object mathOp(Object a, Object b, String op, BinaryOp longOp, BinaryOp doubleOp) {
151         b = coerceType(a, b);
152 
153         if (a instanceof Long) {
154             return longOp.op(a, b);
155         } else if (a instanceof Double) {
156             return doubleOp.op(a, b);
157         }
158 
159         throw new EvalException("'" + op + "' can only operator on numeric types (found '" + a + "' of type " + a.getClass().getName() + ")");
160     }
161 
162     /** Evaluate a TopLevelExpression node. The PRE text in this javadoc corresponds to
163      * the javacc expansion of this node; e.g. in expression.jj, the rule for TopLevelExpression
164      * is
165      *
166      * <pre>
167      * void TopLevelExpression():
168      * {}
169      * {
170      *   Expression() <EOF>
171      * }
172      * </pre>
173      *
174      * <p>jtb then creates two fields in the TopLevelExpression object, "expression" (containing
175      * the Expression() node) and "nodeToken" (containing the EOF token). This is documented
176      * in the form below (this is included for all jtb-generated visit methods):
177      *
178      * <PRE>
179      * expression -> Expression()
180      * nodeToken -> &lt;EOF&gt;
181      * </PRE>
182      */
183     public Object visit(TopLevelExpression n, EvalContext context) {
184         return n.expression.accept(this, context);
185     }
186 
187     /** Evaluate an Expression node.
188      *
189      * <PRE>
190      * conditionalAndExpression -> ConditionalAndExpression()
191      * nodeListOptional -> ( "||" ConditionalAndExpression() )*
192      * </PRE>
193      */
194     public Object visit(Expression n, EvalContext context) {
195         NodeSequence seq;
196         Object lhs;
197         Object rhs;
198 
199         lhs = n.conditionalAndExpression.accept(this, context);
200 
201         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
202             seq = (NodeSequence) e.nextElement();
203             rhs = seq.elementAt(1).accept(this, context);
204 
205             if (!(lhs instanceof Boolean)) {
206                 throw new EvalException("lhs must be boolean");
207             }
208 
209             if (!(rhs instanceof Boolean)) {
210                 throw new EvalException("rhs must be boolean");
211             }
212 
213             // NB: performs short-cut evaluation 
214             boolean result = ((Boolean) lhs).booleanValue() || ((Boolean) rhs).booleanValue();
215 
216             lhs = Boolean.valueOf(result);
217         }
218 
219         return lhs;
220     }
221 
222     /** Evaluate a ConditionalAndExpression node.
223      *
224      * <PRE>
225      * equalityExpression -> EqualityExpression()
226      * nodeListOptional -> ( "&&" EqualityExpression() )*
227      * </PRE>
228      */
229     public Object visit(ConditionalAndExpression n, EvalContext context) {
230         NodeSequence seq;
231         Object lhs;
232         Object rhs;
233 
234         lhs = n.equalityExpression.accept(this, context);
235 
236         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
237             seq = (NodeSequence) e.nextElement();
238             rhs = seq.elementAt(1).accept(this, context);
239             rhs = coerceType(lhs, rhs);
240 
241             if (!(lhs instanceof Boolean)) {
242                 throw new EvalException("lhs must be boolean");
243             }
244 
245             if (!(rhs instanceof Boolean)) {
246                 throw new EvalException("rhs must be boolean");
247             }
248 
249             // NB: performs short-cut evaluation 
250             boolean result = ((Boolean) lhs).booleanValue() && ((Boolean) rhs).booleanValue();
251 
252             lhs = Boolean.valueOf(result);
253         }
254 
255         return lhs;
256     }
257 
258     /** Evaluate a EqualityExpression node.
259      *
260      * <PRE>
261      * relationalExpression -> RelationalExpression()
262      * nodeListOptional -> ( ( "==" | "!=" ) RelationalExpression() )*
263      * </PRE>
264      */
265     @SuppressWarnings("unchecked")
266 	public Object visit(EqualityExpression n, EvalContext context) {
267         NodeSequence seq;
268         Object lhs;
269         Object rhs;
270         // String[] ops = { "==", "!=" };
271 
272         lhs = n.relationalExpression.accept(this, context);
273 
274         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
275             seq = (NodeSequence) e.nextElement();
276             rhs = seq.elementAt(1).accept(this, context);
277             rhs = coerceType(lhs, rhs);
278 
279             if (!(lhs==null || lhs instanceof Comparable)) {
280                 throw new EvalException("Cannot compare lhs");
281             }
282 
283             if (!(rhs==null || rhs instanceof Comparable)) {
284                 // this may not be required (only lhs needs to be comparable to run .compareTo)
285                 throw new EvalException("Cannot compare rhs");
286             }
287 
288             int which = ((NodeChoice) seq.elementAt(0)).which;
289             
290             // this may throw an IllegalStateException if they're not comparable after all
291             int result = -1;
292             try {
293             	// perform null checks first (allow nulls in either lhs or rhs of equality check)
294             	if (lhs==null) {
295             		return (which==0) ? Boolean.valueOf(rhs==null) : Boolean.valueOf(rhs!=null);
296             	} else if (rhs==null) {
297 					return (which==0) ? Boolean.valueOf(lhs==null) : Boolean.valueOf(lhs!=null);
298             	}
299                 result = ((Comparable<Object>) lhs).compareTo(rhs);
300             } catch (IllegalStateException ise) {
301                 throw (EvalException) new EvalException("Cannot perform evaluation").initCause(ise);
302             }
303 
304             // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs);
305             switch (which) {
306                 case 0: lhs = Boolean.valueOf(result == 0); break;
307                 case 1: lhs = Boolean.valueOf(result != 0); break;
308                 default:
309                     throw new EvalException("Internal error - unexpected relational operation");
310             }
311         }
312 
313         return lhs;
314     }
315 
316     /** Evaluate a RelationalExpression node.
317      *
318      * <PRE>
319      * additiveExpression -> AdditiveExpression()
320      * nodeListOptional -> ( ( "&lt;" | "&gt;" | "&lt;=" | "&gt;=" ) AdditiveExpression() )*
321      * </PRE>
322      */
323     @SuppressWarnings("unchecked")
324 	public Object visit(RelationalExpression n, EvalContext context) {
325         NodeSequence seq;
326         Object lhs;
327         Object rhs;
328         // String[] ops = { "<", ">", "<=", ">=" };
329 
330         lhs = n.additiveExpression.accept(this, context);
331 
332         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
333             seq = (NodeSequence) e.nextElement();
334             rhs = seq.elementAt(1).accept(this, context);
335             rhs = coerceType(lhs, rhs);
336 
337             if (!(lhs instanceof Comparable)) {
338                 throw new EvalException("Cannot compare lhs (found type '" + lhs.getClass().getName() + "')");
339             }
340 
341             if (!(rhs instanceof Comparable)) {
342                 // this may not be required (only lhs needs to be comparable to run .compareTo)
343                 throw new EvalException("Cannot compare rhs");
344             }
345 
346             int which = ((NodeChoice) seq.elementAt(0)).which;
347             int result = -1;
348             try {
349                 result = ((Comparable<Object>) lhs).compareTo(rhs);
350             } catch (IllegalStateException ise) {
351                 throw (EvalException) new EvalException("Cannot perform evaluation").initCause(ise);
352             }
353 
354             // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs);
355             switch (which) {
356                 case 0: lhs = Boolean.valueOf(result < 0); break;
357                 case 1: lhs = Boolean.valueOf(result > 0); break;
358                 case 2: lhs = Boolean.valueOf(result <= 0); break;
359                 case 3: lhs = Boolean.valueOf(result >= 0); break;
360                 default:
361                     throw new EvalException("Internal error - unexpected relational operation");
362             }
363         }
364 
365         return lhs;
366     }
367 
368     /** Evaluate a AdditiveExpression node.
369      *
370      * <PRE>
371      * multiplicativeExpression -> MultiplicativeExpression()
372      * nodeListOptional -> ( ( "+" | "-" ) MultiplicativeExpression() )*
373      * </PRE>
374      */
375     public Object visit(AdditiveExpression n, EvalContext context) {
376         NodeSequence seq;
377         Object lhs;
378         Object rhs;
379         // String[] ops = { "+", "-" };
380 
381         lhs = n.multiplicativeExpression.accept(this, context);
382 
383         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
384             seq = (NodeSequence) e.nextElement();
385             rhs = seq.elementAt(1).accept(this, context);
386 
387             int which = ((NodeChoice) seq.elementAt(0)).which;
388 
389             // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs);
390             switch (which) {
391                 case 0:
392                     if (lhs instanceof String) {
393                         // String addition
394                         lhs = ((String) lhs) + rhs.toString();
395                     } else {
396                         // Numeric addition
397                         lhs = mathOp(lhs, rhs, "+", 
398                           new LongOp() { public long longOp(long a, long b) { return a + b; } }, 
399                           new DoubleOp() { public double doubleOp(double a, double b) { return a + b; } });
400                     }
401                     break;
402                 case 1:
403                     lhs = mathOp(lhs, rhs, "-", 
404                       new LongOp() { public long longOp(long a, long b) { return a - b; } }, 
405                       new DoubleOp() { public double doubleOp(double a, double b) { return a - b; } });
406                     break;
407                 default:
408                     throw new EvalException("Internal error - unexpected additive operation");
409             }
410         }
411 
412         return lhs;
413     }
414 
415     /** Evaluate a MultiplicativeExpression node.
416      *
417      * <PRE>
418      * unaryExpression -> UnaryExpression()
419      * nodeListOptional -> ( ( "*" | "/" | "%" ) UnaryExpression() )*
420      * </PRE>
421      */
422     public Object visit(MultiplicativeExpression n, EvalContext context) {
423         NodeSequence seq;
424         Object lhs;
425         Object rhs;
426         // String[] ops = { "*", "/", "%" };
427 
428         lhs = n.unaryExpression.accept(this, context);
429 
430         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
431             seq = (NodeSequence) e.nextElement();
432             rhs = seq.elementAt(1).accept(this, context);
433 
434             int which = ((NodeChoice) seq.elementAt(0)).which;
435 
436             // logger.debug("Running op '" + ops[which] + "' on lhs:" + lhs + ", rhs:" + rhs);
437             switch (which) {
438                 case 0:
439                     lhs = mathOp(lhs, rhs, "*", 
440                       new LongOp() { public long longOp(long a, long b) { return a * b; } }, 
441                       new DoubleOp() { public double doubleOp(double a, double b) { return a * b; } });
442                     break;
443                 case 1:
444                     lhs = mathOp(lhs, rhs, "/", 
445                       new LongOp() { public long longOp(long a, long b) { return a / b; } }, 
446                       new DoubleOp() { public double doubleOp(double a, double b) { return a / b; } });
447                     break;
448                 case 2:
449                     lhs = mathOp(lhs, rhs, "%", 
450                       new LongOp() { public long longOp(long a, long b) { return a % b; } }, 
451                       new DoubleOp() { public double doubleOp(double a, double b) { return a % b; } });
452                     break;
453                 default:
454                     throw new EvalException("Internal error - unexpected additive operation");
455             }
456         }
457 
458         return lhs;
459     }
460 
461     /** Evaluate a UnaryExpression node.
462      *
463      * <PRE>
464      * nodeChoice -> ( "~" | "!" | "-" ) UnaryExpression()
465      *       | PrimaryExpression()
466      * </PRE>
467      */
468     public Object visit(UnaryExpression n, EvalContext context) {
469         NodeSequence seq;
470         Object lhs;
471 
472         if (n.nodeChoice.which == 0) {
473             seq = (NodeSequence) n.nodeChoice.choice;
474 
475             int which = ((NodeChoice) seq.elementAt(0)).which;
476 
477             // String op = ((NodeToken) ((NodeChoice) nl.nodes.get(0)).choice).tokenImage;
478             switch (which) {
479                 case 0:
480                     // long rhs =  makeNumeric ( ((Node) nl.nodes.get(0)).accept(this, context) ); 
481                     // return Long.valueOf(~rhs); 
482                     throw new EvalException("~ not supported");
483                 case 1:
484                     lhs = seq.elementAt(1).accept(this, context);
485                     if (!(lhs instanceof Boolean)) {
486                         throw new EvalException("boolean type expected");
487                     }
488                     return Boolean.valueOf(!((Boolean) lhs).booleanValue());
489                 case 2:
490                     lhs = seq.elementAt(1).accept(this, context);
491                     if (!(lhs instanceof Number)) {
492                         throw new EvalException("numeric type expected");
493                     }
494                     return mathOp(lhs, null, "-", new 
495                       LongOp() { public long longOp(long a, long b) { return -a; } }, 
496                       new DoubleOp() { public double doubleOp(double a, double b) { return -a; } } );
497                 default:
498                     throw new EvalException("Internal error - unexpected unary operation");
499             }
500         } else {
501             return n.nodeChoice.choice.accept(this, context);
502         }
503     }
504 
505     /** Evaluate a PrimaryExpression node.
506      *
507      * <PRE>
508      * nodeChoice -> FunctionCall()
509      *       | Name()
510      *       | Literal()
511      *       | "(" Expression() ")"
512      * </PRE>
513      */
514     public Object visit(PrimaryExpression n, EvalContext context) {
515         NodeSequence seq;
516         int which = n.nodeChoice.which;
517 
518         switch (which) {
519             case 0:
520             case 1:
521             case 2:
522                 return n.nodeChoice.choice.accept(this, context);
523             case 3:
524                 seq = (NodeSequence) n.nodeChoice.choice;
525                 Object obj = seq.elementAt(1).accept(this, context);
526 
527                 ;
528 
529                 return obj;
530             default:
531                 throw new EvalException("Internal parser error (PrimaryExpression)");
532         }
533     }
534 
535     /** Evaluate a Name node.
536      *
537      * <PRE>
538      * nodeToken -> &lt;IDENTIFIER&gt;
539      * nodeListOptional -> ( "." &lt;IDENTIFIER&gt; )*
540      * </PRE>
541      */
542     public Object visit(Name n, EvalContext context) {
543         EvalContext evalContext = (EvalContext) context;
544         String varComponentName;
545         String varBaseName = n.nodeToken.tokenImage;
546 
547         // logger.debug("Fetching var '" + varBaseName + "'");
548         if (evalContext == null) {
549             throw new EvalException("Cannot retrieve variable '" + varBaseName + "' with a null evalContext");
550         }
551 
552         if (!evalContext.hasVariable(varBaseName)) {
553             throw new EvalException("Unknown variable '" + varBaseName + "'");
554         }
555 
556         Object value = evalContext.getVariable(varBaseName);
557         NodeSequence seq;
558 
559         /*
560            if (value == null) {
561                logger.debug(" = null");
562            } else {
563                logger.debug(" = " + value.getClass().getName() + " with value '" + value + "'");
564            }
565          */
566         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
567             seq = (NodeSequence) e.nextElement();
568             varComponentName = ((NodeToken) seq.elementAt(1)).tokenImage;
569 
570             //logger.debug("Fetching component '" + varComponentName + "' from var '" + varBaseName + "'");
571             if (!evalContext.hasVariableComponent(value, varBaseName, varComponentName)) {
572                 throw new EvalException("Unknown variable component '" + varComponentName + "' in variable '" + varBaseName + "'");
573             }
574 
575             value = evalContext.getVariableComponent(value, varBaseName, varComponentName);
576             varBaseName = varBaseName + "." + varComponentName;
577         }
578 
579         return value;
580     }
581 
582     /** Evaluate a FunctionCall node.
583      *
584      * <PRE>
585      * nodeToken -> &lt;IDENTIFIER&gt;
586      * arguments -> Arguments()
587      * </PRE>
588      */
589     public Object visit(FunctionCall n, EvalContext context) {
590         @SuppressWarnings("unchecked")
591 		List<Object> argumentList = (List<Object>) n.arguments.accept(this, context);
592         String functionName = n.nodeToken.tokenImage;
593         EvalContext evalContext = (EvalContext) context;
594         EvalFunction function = (EvalFunction) evalContext.getFunction(functionName);
595         if (function == null) {
596             throw new EvalException("Unknown function '" + functionName + "'");
597         }
598         /*
599         if (evalContext.getVariable(VAR_EVALUATOR) != null) {
600             evalContext.setVariable(VAR_EVALUATOR, this);
601         }
602         */
603         return function.evaluate(functionName, evalContext, argumentList);
604     }
605 
606     /** Evaluates an Arguments node
607      *
608      * <PRE>
609      * nodeToken -> "("
610      * nodeOptional -> [ ArgumentList() ]
611      * nodeToken1 -> ")"
612      * </PRE>
613      */
614     public Object visit(Arguments n, EvalContext context) {
615         if (n.nodeOptional.present()) {
616             return n.nodeOptional.accept(this, context);
617         } else {
618             return new ArrayList<Object>(0);
619         }
620     }
621 
622     /** Evaluate a ArgumentList node.
623      *
624      * <PRE>
625      * expression -> Expression()
626      * nodeListOptional -> ( "," Expression() )*
627      * </PRE>
628      */
629     public Object visit(ArgumentList n, EvalContext context) {
630         NodeSequence seq;
631         List<Object> arguments = new ArrayList<Object>();
632 
633         arguments.add(n.expression.accept(this, context));
634 
635         for (Enumeration<Node> e = n.nodeListOptional.elements(); e.hasMoreElements();) {
636             seq = (NodeSequence) e.nextElement();
637             arguments.add(seq.elementAt(1).accept(this, context));
638         }
639 
640         return arguments;
641     }
642 
643     /** Evaluate a Literal node.
644      *
645      * <PRE>
646      * nodeChoice -> &lt;INTEGER_LITERAL&gt;
647      *       | &lt;FLOATING_POINT_LITERAL&gt;
648      *       | &lt;CHARACTER_LITERAL&gt;
649      *       | &lt;STRING_LITERAL&gt;
650      *       | BooleanLiteral()
651      *       | NullLiteral()
652      * </PRE>
653      *
654      */
655     public Object visit(Literal n, EvalContext context) {
656         String token = null;
657 
658         if (n.nodeChoice.choice instanceof NodeToken) {
659             token = ((NodeToken) n.nodeChoice.choice).tokenImage;
660         }
661 
662         switch (n.nodeChoice.which) {
663             case 0:
664                 return Long.valueOf(token);
665             case 1:
666                 return Double.valueOf(token);
667             case 2:
668                 return Character.valueOf(token.charAt(1));
669             case 3:
670                 return Text.unescapeJava(token.substring(1, token.length() - 1));
671         }
672 
673         // must be 'true', 'false', or 'null'
674         return n.nodeChoice.accept(this, context);
675     }
676 
677     /** Evaluate a BooleanLiteral node.
678      *
679      * <PRE>
680      * nodeChoice -> "true"
681      *       | "false"
682      * </PRE>
683      */
684     public Object visit(BooleanLiteral n, EvalContext context) {
685         if (n.nodeChoice.which == 0) {
686             return Boolean.valueOf(true);
687         } else {
688             return Boolean.valueOf(false);
689         }
690     }
691 
692     /** Evaluate a NullLiteral node.
693      *
694      * <PRE>
695      * nodeToken -> "null"
696      * </PRE>
697      */
698     public Object visit(NullLiteral n, EvalContext context) {
699         return null;
700     }
701 
702     /** This is never executed (we do not evaluate tokens) */
703     public Object visit(NodeToken n, EvalContext context) {
704         return n.tokenImage;
705     }
706 }