001package com.randomnoun.common.jexl.eval;
002
003/* (c) 2013 randomnoun. All Rights Reserved. This work is licensed under a
004 * BSD Simplified License. (http://www.randomnoun.com/bsd-simplified.html)
005 */
006
007import java.math.BigDecimal;
008import java.text.*; // for SimpleDateFormat & friends
009import java.util.*;
010
011
012
013import org.apache.log4j.Logger;
014
015import com.randomnoun.common.Text;
016import com.randomnoun.common.jexl.ast.*;
017import com.randomnoun.common.jexl.visitor.*;
018
019
020/**
021 * This class traverses (and evaluates) an expression AST graph. It
022 * traverses the AST in depth-first order (by extending the ObjectDepthFirst
023 * visitor class generated by jtb). This class implements the
024 * 'Visitor' design pattern.
025 *
026 * <p>Each visit() method in this class takes two parameters, the
027 * first being the node being visited, and the second contains
028 * an EvalContext object containing the variables and functions which can
029 * be referenced within the expression. The visitor (i.e. this class) is then
030 * passed to it's children nodes using the nodes' accept() method (generated
031 * by jtb).
032 *
033 * <p>Variables can be any wrappered primitive type (e.g. Long, Integer, ...),
034 * although mathematical evaluation (+, -, /, etc) are currently only
035 * implemented for Doubles and Longs.
036 *
037 * <p>This class can be considered thread-safe, since it holds no instance-wide state.
038 * (All methods that require access to a EvalContext have it passed in through a
039 * method parameter). i.e. many threads may use the same Evaluator instance to
040 * evaluate different expressions.
041 *
042 * <p>NB: In the javadocs below, "lhs" refers to the left-hand-side value of any binary
043 * operation, and "rhs" refers to the right-hand-side value; e.g. in "3 + 4",
044 * the lhs is "3", the rhs is "4" and the op is "+".
045 * 
046 * @author knoxg
047 */
048public class Evaluator
049    extends GJDepthFirst<Object, EvalContext> {
050
051    // We used to create a variable with this name before executing any function calls 
052        // containing the Evaluator instance that is being used to evaluate the function. Not entirely sure why any more, so have commented it out.
053    // public static final String VAR_EVALUATOR = ".evaluator";
054
055
056    /** Logger instance for this class */
057    Logger logger = Logger.getLogger(Evaluator.class);
058
059    // could probably use generics these days
060    // and I'm pretty sure these are in java.util.functions as well
061    
062    /** Binary operation interface; takes two parameters, returns a result. */
063    interface BinaryOp {
064        Object op(Object a, Object b);
065    }
066
067    /** A binary operation on two parameters whose types are guaranteed to be Long. */
068    abstract class LongOp implements BinaryOp {
069        public Object op(Object a, Object b) {
070            long longA = ((Long) a).longValue();
071            long longB = (b == null ? 0 : ((Long) b).longValue());
072            return new Long(longOp(longA, longB));
073        }
074        abstract long longOp(long a, long b);
075    }
076
077    /** A binary operation on two parameters whose types are guaranteed to be Double. */
078    abstract class DoubleOp implements BinaryOp {
079        public Object op(Object a, Object b) {
080            double doubleA = ((Double) a).doubleValue();
081            double doubleB = (b == null ? 0 : ((Double) b).doubleValue());
082            return new Double(doubleOp(doubleA, doubleB));
083        }
084        abstract double doubleOp(double a, double b);
085    }
086
087    /** Coerce parameter 'b' into a compatible type of parameter 'a', to be used in a
088     *  binary math op. If 'b' can't be coerced into 'a', then throw an EvalException.
089     *
090     * @param a First binary op parameter (the type of which we are casting to)
091     * @param b Second binary op parameter (the value we are casting)
092     * @return The value of 'b', coerced to be the same type of value 'a'
093     */
094    public Object coerceType(Object a, Object b) {
095        // don't coerce nulls
096        if (a == null) { return null; }
097
098        Class<? extends Object> clazz = a.getClass();
099        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 new Double(((Number) b).doubleValue());
116            }
117        }
118
119        if (a instanceof Long) {
120            if (b instanceof Number) {
121                return new Long(((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 = new Boolean(result == 0); break;
307                case 1: lhs = new Boolean(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 = new Boolean(result < 0); break;
357                case 1: lhs = new Boolean(result > 0); break;
358                case 2: lhs = new Boolean(result <= 0); break;
359                case 3: lhs = new Boolean(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 new Long(~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 new Long(token);
665            case 1:
666                return new Double(token);
667            case 2:
668                return new Character(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}