]> source.dussan.org Git - jackcess.git/commitdiff
rework classes, add more interfaces; start implementing builtin functions using Value...
authorJames Ahlborn <jtahlborn@yahoo.com>
Thu, 3 Nov 2016 20:13:35 +0000 (20:13 +0000)
committerJames Ahlborn <jtahlborn@yahoo.com>
Thu, 3 Nov 2016 20:13:35 +0000 (20:13 +0000)
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1054 f203690c-595d-4dc9-a70b-905162fa7fd2

src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java
src/main/java/com/healthmarketscience/jackcess/util/BuiltinOperators.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/util/DefaultFunctions.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/util/Expression.java [new file with mode: 0644]
src/main/java/com/healthmarketscience/jackcess/util/ExpressionTokenizer.java
src/main/java/com/healthmarketscience/jackcess/util/Expressionator.java
src/test/java/com/healthmarketscience/jackcess/util/ExpressionatorTest.java

index 998e80ac9672f1158d59105156a0ce9abb427de5..d61bab87a1a386301ab4b9b6c87e0d71f223a688 100644 (file)
@@ -1524,6 +1524,15 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
       return false;
     } else if(obj instanceof Boolean) {
       return ((Boolean)obj).booleanValue();
+    } else if(obj instanceof Number) {
+      // Access considers 0 as "false"
+      if(obj instanceof BigDecimal) {
+        return (((BigDecimal)obj).compareTo(BigDecimal.ZERO) != 0);
+      } 
+      if(obj instanceof BigInteger) {
+        return (((BigInteger)obj).compareTo(BigInteger.ZERO) != 0);
+      }
+      return (((Number)obj).doubleValue() != 0.0);
     }
     return Boolean.parseBoolean(obj.toString());
   }
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/util/BuiltinOperators.java
new file mode 100644 (file)
index 0000000..cb7eb6c
--- /dev/null
@@ -0,0 +1,437 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.util;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import com.healthmarketscience.jackcess.RuntimeIOException;
+import com.healthmarketscience.jackcess.impl.ColumnImpl;
+import com.healthmarketscience.jackcess.util.Expression.*;
+
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class BuiltinOperators 
+{
+
+  public static final Value NULL_VAL = 
+    new SimpleValue(ValueType.NULL, null);
+  public static final Value TRUE_VAL = 
+    new SimpleValue(ValueType.BOOLEAN, Boolean.TRUE);
+  public static final Value FALSE_VAL = 
+    new SimpleValue(ValueType.BOOLEAN, Boolean.FALSE);
+
+  public static class SimpleValue implements Value
+  {
+    private final ValueType _type;
+    private final Object _val;
+
+    public SimpleValue(ValueType type, Object val) {
+      _type = type;
+      _val = val;
+    }
+
+    public ValueType getType() {
+      return _type;
+    }
+
+    public Object get() {
+      return _val;
+    }
+  }
+
+  private BuiltinOperators() {}
+
+  // FIXME, null propagation:
+  // http://www.utteraccess.com/wiki/index.php/Nulls_And_Their_Behavior
+  // https://theaccessbuddy.wordpress.com/2012/10/24/6-logical-operators-in-ms-access-that-you-must-know-operator-types-3-of-5/
+  // - number ops
+  // - comparison ops
+  // - logical ops (some "special")
+  //   - And - can be false if one arg is false
+  //   - Or - can be true if one arg is true
+  // - between, not, like, in
+  // - *NOT* concal op '&'
+  // FIXME, Imp operator?
+
+  public static Value negate(Value param1) {
+    // FIXME
+    return null;
+  }
+
+  public static Value add(Value param1, Value param2) {
+    // FIXME
+    return null;
+  }
+
+  public static Value subtract(Value param1, Value param2) {
+    // FIXME
+    return null;
+  }
+
+  public static Value multiply(Value param1, Value param2) {
+    // FIXME
+    return null;
+  }
+
+  public static Value divide(Value param1, Value param2) {
+    // FIXME
+    return null;
+  }
+
+  public static Value intDivide(Value param1, Value param2) {
+    // FIXME
+    return null;
+  }
+
+  public static Value exp(Value param1, Value param2) {
+    // FIXME
+    return null;
+  }
+
+  public static Value concat(Value param1, Value param2) {
+    // note, this op converts null to empty string
+    
+
+    // FIXME
+    return null;
+  }
+
+  public static Value mod(Value param1, Value param2) {
+    // FIXME
+    return null;
+  }
+
+  public static Value not(Value param1) {
+    if(paramIsNull(param1)) {
+      // null propagation
+      return NULL_VAL;
+    }
+    
+    return toValue(!nonNullValueToBoolean(param1));
+  }
+
+  public static Value lessThan(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullCompareTo(param1, param2) < 0);
+  }
+
+  public static Value greaterThan(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullCompareTo(param1, param2) > 0);
+  }
+
+  public static Value lessThanEq(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullCompareTo(param1, param2) <= 0);
+  }
+
+  public static Value greaterThanEq(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullCompareTo(param1, param2) >= 0);
+  }
+
+  public static Value equals(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullCompareTo(param1, param2) == 0);
+  }
+
+  public static Value notEquals(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullCompareTo(param1, param2) != 0);
+  }
+
+  public static Value and(Value param1, Value param2) {
+
+    // "and" uses short-circuit logic
+
+    if(paramIsNull(param1)) {
+      return NULL_VAL;
+    }
+
+    boolean b1 = nonNullValueToBoolean(param1);
+    if(!b1) {
+      return FALSE_VAL;
+    }
+
+    if(paramIsNull(param2)) {
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullValueToBoolean(param2));
+  }
+
+  public static Value or(Value param1, Value param2) {
+
+    // "or" uses short-circuit logic
+
+    if(paramIsNull(param1)) {
+      return NULL_VAL;
+    }
+
+    boolean b1 = nonNullValueToBoolean(param1);
+    if(b1) {
+      return TRUE_VAL;
+    }
+
+    if(paramIsNull(param2)) {
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullValueToBoolean(param2));
+  }
+
+  public static Value eqv(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    boolean b1 = nonNullValueToBoolean(param1);
+    boolean b2 = nonNullValueToBoolean(param2);
+
+    return toValue(b1 == b2);
+  }
+
+  public static Value xor(Value param1, Value param2) {
+    if(anyParamIsNull(param1, param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    boolean b1 = nonNullValueToBoolean(param1);
+    boolean b2 = nonNullValueToBoolean(param2);
+
+    return toValue(b1 ^ b2);
+  }
+
+  public static Value imp(Value param1, Value param2) {
+
+    // "imp" uses short-circuit logic
+
+    if(paramIsNull(param1)) {
+      if(paramIsNull(param2) || !nonNullValueToBoolean(param2)) {
+        // null propagation
+        return NULL_VAL;
+      }
+
+      return TRUE_VAL;
+    }
+
+    boolean b1 = nonNullValueToBoolean(param1);
+    if(!b1) {
+      return TRUE_VAL;
+    }
+
+    if(paramIsNull(param2)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    return toValue(nonNullValueToBoolean(param2));
+  }
+
+  public static Value isNull(Value param1) {
+    return toValue(param1.getType() == ValueType.NULL);
+  }
+
+  public static Value isNotNull(Value param1) {
+    return toValue(param1.getType() == ValueType.NULL);
+  }
+
+  public static Value like(Value param1, Pattern pattern) {
+    // FIXME
+    return null;
+  }
+
+  public static Value between(Value param1, Value param2, Value param3) {
+
+    // null propagate any field left to right.  uses short circuit eval
+    if(anyParamIsNull(param1, param2, param3)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    // FIXME
+    return null;
+  }
+
+  public static Value notBetween(Value param1, Value param2, Value param3) {
+    return not(between(param1, param2, param3));
+  }
+
+  public static Value in(Value param1, Value[] params) {
+
+    // null propagate any param.  uses short circuit eval of params
+    if(paramIsNull(param1)) {
+      // null propagation
+      return NULL_VAL;
+    }
+
+    for(Value val : params) {
+      if(paramIsNull(val)) {
+        continue;
+      }
+
+      // FIXME test
+    }
+
+    // FIXME
+    return null;
+  }
+
+  public static Value notIn(Value param1, Value[] params) {
+    return not(in(param1, params));
+  }
+
+  
+  private static boolean anyParamIsNull(Value param1, Value param2) {
+    return (paramIsNull(param1) || paramIsNull(param2));
+  }
+
+  private static boolean anyParamIsNull(Value param1, Value param2,
+                                        Value param3) {
+    return (paramIsNull(param1) || paramIsNull(param2) || paramIsNull(param3));
+  }
+
+  private static boolean paramIsNull(Value param1) {
+    return (param1.getType() == ValueType.NULL);
+  }
+
+  protected static CharSequence paramToString(Object param) {
+    try {
+      return ColumnImpl.toCharSequence(param);
+    } catch(IOException e) {
+      throw new RuntimeIOException(e);
+    }
+  }
+
+  protected static boolean paramToBoolean(Object param) {
+    // FIXME, null is false...?
+    return ColumnImpl.toBooleanValue(param);
+  }
+
+  protected static Number paramToNumber(Object param) {
+    // FIXME
+    return null;
+  }
+
+  protected static boolean nonNullValueToBoolean(Value val) {
+    switch(val.getType()) {
+    case BOOLEAN:
+      return (Boolean)val.get();
+    case STRING:
+    case DATE:
+    case TIME:
+    case DATE_TIME:
+      // strings and dates are always true
+      return true;
+    case LONG:
+      return (((Number)val.get()).longValue() != 0L);
+    case DOUBLE:
+      return (((Number)val.get()).doubleValue() != 0.0d);
+    case BIG_INT:
+      return (((BigInteger)val.get()).compareTo(BigInteger.ZERO) != 0L);
+    case BIG_DEC:
+      return (((BigDecimal)val.get()).compareTo(BigDecimal.ZERO) != 0L);
+    default:
+      throw new RuntimeException("Unexpected type " + val.getType());
+    }
+  }
+
+  protected static int nonNullCompareTo(
+      Value param1, Value param2)
+  {
+    // FIXME
+    return 0;
+  }
+
+  public static Value toValue(boolean b) {
+    return (b ? TRUE_VAL : FALSE_VAL);
+  }
+
+  public static Value toValue(Object obj) {
+    if(obj == null) {
+      return NULL_VAL;
+    }
+
+    if(obj instanceof Value) {
+      return (Value)obj;
+    }
+
+    if(obj instanceof Boolean) {
+      return (((Boolean)obj) ? TRUE_VAL : FALSE_VAL);
+    }
+
+    if(obj instanceof Date) {
+      // any way to figure out whether it's a date/time/dateTime?
+      return new SimpleValue(ValueType.DATE_TIME, obj);
+    }
+
+    if(obj instanceof Number) {
+      if((obj instanceof Double) || (obj instanceof Float)) {
+        return new SimpleValue(ValueType.DOUBLE, obj);
+      }
+      if(obj instanceof BigDecimal) {
+        return new SimpleValue(ValueType.BIG_DEC, obj);
+      }
+      if(obj instanceof BigInteger) {
+        return new SimpleValue(ValueType.BIG_INT, obj);
+      }
+      return new SimpleValue(ValueType.LONG, obj);
+    }
+
+    try {
+      return new SimpleValue(ValueType.STRING, 
+                             ColumnImpl.toCharSequence(obj).toString());
+    } catch(IOException e) {
+      throw new RuntimeIOException(e);
+    }
+  }
+
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/util/DefaultFunctions.java
new file mode 100644 (file)
index 0000000..3c9c292
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.util;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+import com.healthmarketscience.jackcess.util.Expression.*;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public class DefaultFunctions 
+{
+  private static final Map<String,Function> FUNCS = 
+    new HashMap<String,Function>();
+
+  private DefaultFunctions() {}
+
+  public static Function getFunction(String name) {
+    return FUNCS.get(name.toLowerCase());
+  }
+
+  public static abstract class BaseFunction implements Function
+  {
+    private final String _name;
+    private final int _minParams;
+    private final int _maxParams;
+
+    protected BaseFunction(String name, int minParams, int maxParams)
+    {
+      _name = name;
+      _minParams = minParams;
+      _maxParams = maxParams;
+    }
+
+    public String getName() {
+      return _name;
+    }
+
+    protected void validateNumParams(Value[] params) {
+      int num = params.length;
+      if((num < _minParams) || (num > _maxParams)) {
+        String range = ((_minParams == _maxParams) ? "" + _minParams :
+                        _minParams + " to " + _maxParams);
+        throw new IllegalArgumentException(
+            this + ": invalid number of parameters " +
+            num + " passed, expected " + range);
+      }
+    }
+
+    protected static CharSequence paramToString(Object param)
+    {
+      return BuiltinOperators.paramToString(param);
+    }
+
+    protected static boolean paramToBoolean(Object param)
+    {
+      return BuiltinOperators.paramToBoolean(param);
+    }
+
+    protected static Number paramToNumber(Object param)
+    {
+      return BuiltinOperators.paramToNumber(param);
+    }
+
+    @Override
+    public String toString() {
+      return getName() + "()";
+    }
+  }
+
+  public static abstract class Func1 extends BaseFunction
+  {
+    protected Func1(String name) {
+      super(name, 1, 1);
+    }
+
+    public final Value eval(Value... params) {
+      validateNumParams(params);
+      return eval1(params[0]);
+    }
+
+    protected abstract Value eval1(Value param);
+  }
+
+  public static abstract class Func2 extends BaseFunction
+  {
+    protected Func2(String name) {
+      super(name, 2, 2);
+    }
+
+    public final Value eval(Value... params) {
+      validateNumParams(params);
+      return eval2(params[0], params[1]);
+    }
+
+    protected abstract Value eval2(Value param1, Value param2);
+  }
+
+  public static abstract class Func3 extends BaseFunction
+  {
+    protected Func3(String name) {
+      super(name, 3, 3);
+    }
+
+    public final Value eval(Value... params) {
+      validateNumParams(params);
+      return eval3(params[0], params[1], params[2]);
+    }
+
+    protected abstract Value eval3(Value param1, Value param2, Value param3);
+  }
+
+  public static final Function IIF = new Func3("IIf") {
+    @Override
+    protected Value eval3(Value param1, Value param2, Value param3) {
+      // FIXME
+      // return (paramToBoolean(param1) ? param2 : param3);
+      return null;
+    }
+  };
+
+  // https://www.techonthenet.com/access/functions/
+  // https://support.office.com/en-us/article/Access-Functions-by-category-b8b136c3-2716-4d39-94a2-658ce330ed83
+
+  private static void registerFunc(Function func) {
+    if(FUNCS.put(func.getName().toLowerCase(), func) != null) {
+      throw new IllegalStateException("Duplicate function " + func);
+    }
+  }
+
+  static {
+    registerFunc(IIF);
+  }
+}
diff --git a/src/main/java/com/healthmarketscience/jackcess/util/Expression.java b/src/main/java/com/healthmarketscience/jackcess/util/Expression.java
new file mode 100644 (file)
index 0000000..f2bb462
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+Copyright (c) 2016 James Ahlborn
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package com.healthmarketscience.jackcess.util;
+
+/**
+ *
+ * @author James Ahlborn
+ */
+public interface Expression 
+{
+  public interface RowContext
+  {
+    public Value getThisColumnValue();
+
+    public Value getRowValue(String collectionName, String objName,
+                             String colName);
+  }
+
+  public enum ValueType
+  {
+    NULL, BOOLEAN, STRING, DATE, TIME, DATE_TIME, LONG, DOUBLE, BIG_INT, BIG_DEC;
+
+    public boolean isNumeric() {
+      return inRange(LONG, BIG_DEC);
+    }
+
+    public boolean isTemporal() {
+      return inRange(DATE, DATE_TIME);
+    }
+
+    private boolean inRange(ValueType start, ValueType end) {
+      return ((start.ordinal() <= ordinal()) && (ordinal() <= end.ordinal()));
+    }
+  }
+
+  public interface Value
+  {
+    public ValueType getType();
+    public Object get();
+  }
+
+  public interface Function
+  {
+    public String getName();
+    public Value eval(Value... params);
+  }
+
+
+  public Object evalDefault();
+
+  public Boolean evalCondition(RowContext ctx);
+
+  public String toDebugString();
+}
index b9a3cd49d56df5bf2eada7271466384abc02c070..192a62d073d81da4cdc83a4b8e3966d014e64658 100644 (file)
@@ -27,9 +27,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import com.healthmarketscience.jackcess.DatabaseBuilder;
-import com.healthmarketscience.jackcess.impl.DatabaseImpl;
 import static com.healthmarketscience.jackcess.util.Expressionator.*;
+import com.healthmarketscience.jackcess.util.Expression.ValueType;
+
 
 /**
  *
@@ -74,7 +74,8 @@ class ExpressionTokenizer
    * Tokenizes an expression string of the given type and (optionally) in the
    * context of the relevant database.
    */
-  static List<Token> tokenize(Type exprType, String exprStr, DatabaseImpl db) {
+  static List<Token> tokenize(Type exprType, String exprStr,
+                              ParseContext context) {
 
     if(exprStr != null) {
       exprStr = exprStr.trim();
@@ -99,10 +100,9 @@ class ExpressionTokenizer
         case IS_OP_FLAG:
 
           // special case '-' for negative number
-          Map.Entry<?,String> numLit = maybeParseNumberLiteral(c, buf);
+          Token numLit = maybeParseNumberLiteral(c, buf);
           if(numLit != null) {
-            tokens.add(new Token(TokenType.LITERAL, numLit.getKey(), 
-                                 numLit.getValue()));
+            tokens.add(numLit);
             continue;
           }
           
@@ -152,12 +152,11 @@ class ExpressionTokenizer
 
           switch(c) {
           case QUOTED_STR_CHAR:
-            tokens.add(new Token(TokenType.LITERAL, parseQuotedString(buf)));
+            tokens.add(new Token(TokenType.LITERAL, null, parseQuotedString(buf),
+                                 ValueType.STRING));
             break;
           case DATE_LIT_QUOTE_CHAR:
-            Map.Entry<?,String> dateLit = parseDateLiteralString(buf, db);
-            tokens.add(new Token(TokenType.LITERAL, dateLit.getKey(), 
-                                 dateLit.getValue()));
+            tokens.add(parseDateLiteralString(buf, context));
             break;
           case OBJ_NAME_START_CHAR:
             tokens.add(new Token(TokenType.OBJ_NAME, parseObjNameString(buf)));
@@ -176,10 +175,9 @@ class ExpressionTokenizer
       } else {
 
         if(isDigit(c)) {
-          Map.Entry<?,String> numLit = maybeParseNumberLiteral(c, buf);
+          Token numLit = maybeParseNumberLiteral(c, buf);
           if(numLit != null) {
-            tokens.add(new Token(TokenType.LITERAL, numLit.getKey(), 
-                                 numLit.getValue()));
+            tokens.add(numLit);
             continue;
           }
         }
@@ -309,8 +307,8 @@ class ExpressionTokenizer
     return sb.toString();
   }
 
-  private static Map.Entry<?,String> parseDateLiteralString(
-      ExprBuf buf, DatabaseImpl db
+  private static Token parseDateLiteralString(
+      ExprBuf buf, ParseContext context
   {
     String dateStr = parseStringUntil(buf, DATE_LIT_QUOTE_CHAR, null);
     
@@ -318,27 +316,30 @@ class ExpressionTokenizer
     boolean hasTime = (dateStr.indexOf(':') >= 0);
 
     SimpleDateFormat sdf = null;
+    ValueType valType = null;
     if(hasDate && hasTime) {
-      sdf = buf.getDateTimeFormat(db);
+      sdf = buf.getDateTimeFormat(context);
+      valType = ValueType.DATE_TIME;
     } else if(hasDate) {
-      sdf = buf.getDateFormat(db);
+      sdf = buf.getDateFormat(context);
+      valType = ValueType.DATE;
     } else if(hasTime) {
-      sdf = buf.getTimeFormat(db);
+      sdf = buf.getTimeFormat(context);
+      valType = ValueType.TIME;
     } else {
       throw new IllegalArgumentException("Invalid date time literal " + dateStr +
                                          " " + buf);
     }
 
-    // FIXME, do we need to know which "type" it was?
     try {
-      return newEntry(sdf.parse(dateStr), dateStr);
+      return new Token(TokenType.LITERAL, sdf.parse(dateStr), dateStr, valType);
     } catch(ParseException pe) {
       throw new IllegalArgumentException(       
           "Invalid date time literal " + dateStr + " " + buf, pe);
     }
   }
 
-  private static Map.Entry<?,String> maybeParseNumberLiteral(char firstChar, ExprBuf buf) {
+  private static Token maybeParseNumberLiteral(char firstChar, ExprBuf buf) {
     StringBuilder sb = buf.getScratchBuffer().append(firstChar);
     boolean hasDigit = isDigit(firstChar);
 
@@ -374,7 +375,7 @@ class ExpressionTokenizer
         // what number type to use here?
         BigDecimal num = new BigDecimal(numStr);
         foundNum = true;
-        return newEntry(num, numStr);
+        return new Token(TokenType.LITERAL, num, numStr, ValueType.BIG_DEC);
       } catch(NumberFormatException ne) {
         throw new IllegalArgumentException(
             "Invalid number literal " + numStr + " " + buf, ne);
@@ -458,32 +459,27 @@ class ExpressionTokenizer
       return _scratch;
     }
 
-    public SimpleDateFormat getDateFormat(DatabaseImpl db) {
+    public SimpleDateFormat getDateFormat(ParseContext context) {
       if(_dateFmt == null) {
-        _dateFmt = newFormat(DATE_FORMAT, db);
+        _dateFmt = context.createDateFormat(DATE_FORMAT);
       }
       return _dateFmt;
     }
 
-    public SimpleDateFormat getTimeFormat(DatabaseImpl db) {
+    public SimpleDateFormat getTimeFormat(ParseContext context) {
       if(_timeFmt == null) {
-        _timeFmt = newFormat(TIME_FORMAT, db);
+        _timeFmt = context.createDateFormat(TIME_FORMAT);
       }
       return _timeFmt;
     }
 
-    public SimpleDateFormat getDateTimeFormat(DatabaseImpl db) {
+    public SimpleDateFormat getDateTimeFormat(ParseContext context) {
       if(_dateTimeFmt == null) {
-        _dateTimeFmt = newFormat(DATE_TIME_FORMAT, db);
+        _dateTimeFmt = context.createDateFormat(DATE_TIME_FORMAT);
       }
       return _dateTimeFmt;
     }
 
-    private static SimpleDateFormat newFormat(String str, DatabaseImpl db) {
-      return ((db != null) ? db.createDateFormat(str) :
-              DatabaseBuilder.createDateFormat(str));
-    } 
-
     @Override
     public String toString() {
       return "[char " + _pos + "] '" + _str + "'";
@@ -496,15 +492,21 @@ class ExpressionTokenizer
     private final TokenType _type;
     private final Object _val;
     private final String _valStr;
+    private final ValueType _valType;
 
     private Token(TokenType type, String val) {
       this(type, val, val);
     }
 
     private Token(TokenType type, Object val, String valStr) {
+      this(type, val, valStr, null);
+    }
+
+    private Token(TokenType type, Object val, String valStr, ValueType valType) {
       _type = type;
-      _val = val;
+      _val = ((val != null) ? val : valStr);
       _valStr = valStr;
+      _valType = valType;
     }
 
     public TokenType getType() {
@@ -519,14 +521,18 @@ class ExpressionTokenizer
       return _valStr;
     }
 
+    public ValueType getValueType() {
+      return _valType;
+    }
+
     @Override
     public String toString() {
       if(_type == TokenType.SPACE) {
         return "' '";
       }
       String str = "[" + _type + "] '" + _val + "'";
-      if(_type == TokenType.LITERAL) {
-        str += " (" + _val.getClass() + ")";
+      if(_valType != null) {
+        str += " (" + _valType + ")";
       }
       return str;
     }
index 6b5d3fd2c88e917ac78843405b890370fd0e54c4..286ab8b85bd687c741c525ceea485e96433b6731 100644 (file)
@@ -16,10 +16,10 @@ limitations under the License.
 
 package com.healthmarketscience.jackcess.util;
 
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -30,10 +30,10 @@ import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
 
-import com.healthmarketscience.jackcess.Database;
-import com.healthmarketscience.jackcess.impl.DatabaseImpl;
+import com.healthmarketscience.jackcess.DatabaseBuilder;
 import static com.healthmarketscience.jackcess.util.ExpressionTokenizer.Token;
 import static com.healthmarketscience.jackcess.util.ExpressionTokenizer.TokenType;
+import com.healthmarketscience.jackcess.util.Expression.*;
 
 /**
  *
@@ -54,6 +54,20 @@ public class Expressionator
     DEFAULT_VALUE, FIELD_VALIDATOR, RECORD_VALIDATOR;
   }
 
+  public interface ParseContext {
+    public SimpleDateFormat createDateFormat(String formatStr);
+    public Function getExpressionFunction(String name);
+  }
+
+  public static final ParseContext DEFAULT_PARSE_CONTEXT = new ParseContext() {
+    public SimpleDateFormat createDateFormat(String formatStr) {
+      return DatabaseBuilder.createDateFormat(formatStr);
+    }
+    public Function getExpressionFunction(String name) {
+      return DefaultFunctions.getFunction(name);
+    }
+  };
+
   private enum WordType {
     OP, COMP, LOG_OP, CONST, SPEC_OP_PREFIX, DELIM;
   }
@@ -64,12 +78,13 @@ public class Expressionator
   private static final String CLOSE_PAREN = ")";
   private static final String FUNC_PARAM_SEP = ",";
 
-  private static final Map<String,WordType> WORD_TYPES = new HashMap<String,WordType>();
+  private static final Map<String,WordType> WORD_TYPES = 
+    new HashMap<String,WordType>();
 
   static {
     setWordType(WordType.OP, "+", "-", "*", "/", "\\", "^", "&", "mod");
     setWordType(WordType.COMP, "<", "<=", ">", ">=", "=", "<>");
-    setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor");
+    setWordType(WordType.LOG_OP, "and", "or", "eqv", "xor", "imp");
     setWordType(WordType.CONST, "true", "false", "null");
     setWordType(WordType.SPEC_OP_PREFIX, "is", "like", "between", "in", "not");
     // "X is null", "X is not null", "X like P", "X between A and B",
@@ -81,7 +96,16 @@ public class Expressionator
   private interface OpType {}
 
   private enum UnaryOp implements OpType {
-    NEG("-", false), NOT("Not", true);
+    NEG("-", false) {
+      @Override public Value eval(Value param1) {
+        return BuiltinOperators.negate(param1);
+      }
+    },
+    NOT("Not", true) {
+      @Override public Value eval(Value param1) {
+        return BuiltinOperators.not(param1);
+      }
+    };
 
     private final String _str;
     private final boolean _needSpace;
@@ -99,11 +123,51 @@ public class Expressionator
     public String toString() {
       return _str;
     }
+
+    public abstract Value eval(Value param1);
   }
 
   private enum BinaryOp implements OpType {
-    PLUS("+"), MINUS("-"), MULT("*"), DIV("/"), INT_DIV("\\"), EXP("^"), 
-    CONCAT("&"), MOD("Mod");
+    PLUS("+") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.add(param1, param2);
+      }
+    }, 
+    MINUS("-") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.subtract(param1, param2);
+      }
+    }, 
+    MULT("*") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.multiply(param1, param2);
+      }
+    },
+    DIV("/") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.divide(param1, param2);
+      }
+    }, 
+    INT_DIV("\\") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.intDivide(param1, param2);
+      }
+    },
+    EXP("^") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.exp(param1, param2);
+      }
+    }, 
+    CONCAT("&") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.concat(param1, param2);
+      }
+    }, 
+    MOD("Mod") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.mod(param1, param2);
+      }
+    };
 
     private final String _str;
 
@@ -115,10 +179,41 @@ public class Expressionator
     public String toString() {
       return _str;
     }
+
+    public abstract Value eval(Value param1, Value param2);
   }
 
   private enum CompOp implements OpType {
-    LT("<"), LTE("<="), GT(">"), GTE(">="), EQ("="), NE("<>");
+    LT("<") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.lessThan(param1, param2);
+      }
+    }, 
+    LTE("<=") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.lessThanEq(param1, param2);
+      }
+    },
+    GT(">") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.greaterThan(param1, param2);
+      }
+    }, 
+    GTE(">=") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.greaterThanEq(param1, param2);
+      }
+    }, 
+    EQ("=") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.equals(param1, param2);
+      }
+    }, 
+    NE("<>") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.notEquals(param1, param2);
+      }
+    };
 
     private final String _str;
 
@@ -130,10 +225,36 @@ public class Expressionator
     public String toString() {
       return _str;
     }
+
+    public abstract Value eval(Value param1, Value param2);
   }
 
   private enum LogOp implements OpType {
-    AND("And"), OR("Or"), EQV("Eqv"), XOR("Xor");
+    AND("And") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.and(param1, param2);
+      }
+    }, 
+    OR("Or") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.or(param1, param2);
+      }
+    }, 
+    EQV("Eqv") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.eqv(param1, param2);
+      }
+    }, 
+    XOR("Xor") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.xor(param1, param2);
+      }
+    },
+    IMP("Imp") {
+      @Override public Value eval(Value param1, Value param2) {
+        return BuiltinOperators.imp(param1, param2);
+      }
+    };
 
     private final String _str;
 
@@ -145,14 +266,53 @@ public class Expressionator
     public String toString() {
       return _str;
     }
+
+    public abstract Value eval(Value param1, Value param2);
   }
 
   private enum SpecOp implements OpType {
     // note, "NOT" is not actually used as a special operation, always
     // replaced with UnaryOp.NOT
-    NOT("Not"), IS_NULL("Is Null"), IS_NOT_NULL("Is Not Null"), LIKE("Like"), 
-    BETWEEN("Between"), NOT_BETWEEN("Not Between"), IN("In"), 
-    NOT_IN("Not In");
+    NOT("Not") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        throw new UnsupportedOperationException();
+      }
+    }, 
+    IS_NULL("Is Null") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        return BuiltinOperators.isNull(param1);
+      }
+    }, 
+    IS_NOT_NULL("Is Not Null") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        return BuiltinOperators.isNotNull(param1);
+      }
+    }, 
+    LIKE("Like") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        return BuiltinOperators.like(param1, (Pattern)param2);
+      }
+    }, 
+    BETWEEN("Between") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        return BuiltinOperators.between(param1, (Value)param2, (Value)param3);
+      }
+    }, 
+    NOT_BETWEEN("Not Between") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        return BuiltinOperators.notBetween(param1, (Value)param2, (Value)param3);
+      }
+    }, 
+    IN("In") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        return BuiltinOperators.in(param1, (Value[])param2);
+      }
+    }, 
+    NOT_IN("Not In") {
+      @Override public Value eval(Value param1, Object param2, Object param3) {
+        return BuiltinOperators.notIn(param1, (Value[])param2);
+      }
+    };
 
     private final String _str;
 
@@ -164,6 +324,8 @@ public class Expressionator
     public String toString() {
       return _str;
     }
+
+    public abstract Value eval(Value param1, Object param2, Object param3);
   }
 
   private static final Map<OpType, Integer> PRECENDENCE = 
@@ -182,6 +344,7 @@ public class Expressionator
         new OpType[]{LogOp.OR},
         new OpType[]{LogOp.XOR},
         new OpType[]{LogOp.EQV},
+        new OpType[]{LogOp.IMP},
         new OpType[]{SpecOp.IN, SpecOp.NOT_IN, SpecOp.BETWEEN, 
                      SpecOp.NOT_BETWEEN});
 
@@ -190,7 +353,7 @@ public class Expressionator
   
 
   private static final Expr THIS_COL_VALUE = new Expr() {
-    @Override protected Object eval(RowContext ctx) {
+    @Override protected Value eval(RowContext ctx) {
       return ctx.getThisColumnValue();
     }
     @Override protected void toExprString(StringBuilder sb, boolean isDebug) {
@@ -198,18 +361,25 @@ public class Expressionator
     }
   };
 
-  private static final Expr NULL_VALUE = new EConstValue(null, "Null");
-  private static final Expr TRUE_VALUE = new EConstValue(Boolean.TRUE, "True");
-  private static final Expr FALSE_VALUE = new EConstValue(Boolean.FALSE, "False");
+  private static final Expr NULL_VALUE = new EConstValue(
+      BuiltinOperators.NULL_VAL, "Null");
+  private static final Expr TRUE_VALUE = new EConstValue(
+      BuiltinOperators.TRUE_VAL, "True");
+  private static final Expr FALSE_VALUE = new EConstValue(
+      BuiltinOperators.FALSE_VAL, "False");
 
   private Expressionator() 
   {
   }
 
-  static String testTokenize(Type exprType, String exprStr, Database db) {
+  static String testTokenize(Type exprType, String exprStr, 
+                             ParseContext context) {
     
+    if(context == null) {
+      context = DEFAULT_PARSE_CONTEXT;
+    }
     List<Token> tokens = trimSpaces(
-        ExpressionTokenizer.tokenize(exprType, exprStr, (DatabaseImpl)db));
+        ExpressionTokenizer.tokenize(exprType, exprStr, context));
 
     if(tokens == null) {
       // FIXME, NULL_EXPR?
@@ -219,7 +389,12 @@ public class Expressionator
     return tokens.toString();
   }
 
-  public static Expr parse(Type exprType, String exprStr, Database db) {
+  public static Expression parse(Type exprType, String exprStr, 
+                                 ParseContext context) {
+
+    if(context == null) {
+      context = DEFAULT_PARSE_CONTEXT;
+    }
 
     // FIXME,restrictions:
     // - default value only accepts simple exprs, otherwise becomes literal text
@@ -228,14 +403,14 @@ public class Expressionator
     // - record validation cannot refer to outside columns
 
     List<Token> tokens = trimSpaces(
-        ExpressionTokenizer.tokenize(exprType, exprStr, (DatabaseImpl)db));
+        ExpressionTokenizer.tokenize(exprType, exprStr, context));
 
     if(tokens == null) {
       // FIXME, NULL_EXPR?
       return null;
     }
 
-    return parseExpression(new TokBuf(exprType, tokens), false);
+    return parseExpression(new TokBuf(exprType, tokens, context), false);
   }
 
   private static List<Token> trimSpaces(List<Token> tokens) {
@@ -274,7 +449,7 @@ public class Expressionator
 
       case LITERAL:
         
-        buf.setPendingExpr(new ELiteralValue(t.getValue()));
+        buf.setPendingExpr(new ELiteralValue(t.getValueType(), t.getValue()));
         break;
         
       case OP:
@@ -456,8 +631,13 @@ public class Expressionator
         
       buf.next();
       List<Expr> params = findParenExprs(buf, true);
-      buf.setPendingExpr(
-          new EFunc(firstTok.getValueStr(), params));
+      String funcName = firstTok.getValueStr();
+      Function func = buf.getFunction(funcName);
+      if(func == null) {
+        throw new IllegalArgumentException("Could not find function '" + 
+                                           funcName + "' " + buf);
+      }
+      buf.setPendingExpr(new EFunc(func, params));
       foundFunc = true;
       return true;
 
@@ -602,7 +782,6 @@ public class Expressionator
 
     Expr expr = buf.takePendingExpr();
 
-    // FIXME
     Expr specOpExpr = null;
     switch(specOp) {
     case IS_NULL:
@@ -774,24 +953,27 @@ public class Expressionator
     private final List<Token> _tokens;
     private final TokBuf _parent;
     private final int _parentOff;
+    private final ParseContext _context;
     private int _pos;
     private Expr _pendingExpr;
     private final boolean _simpleExpr;
 
-    private TokBuf(Type exprType, List<Token> tokens) {
-      this(exprType, false, tokens, null, 0);
+    private TokBuf(Type exprType, List<Token> tokens, ParseContext context) {
+      this(exprType, false, tokens, null, 0, context);
     }
 
     private TokBuf(List<Token> tokens, TokBuf parent, int parentOff) {
-      this(parent._exprType, parent._simpleExpr, tokens, parent, parentOff);
+      this(parent._exprType, parent._simpleExpr, tokens, parent, parentOff,
+           parent._context);
     }
 
     private TokBuf(Type exprType, boolean simpleExpr, List<Token> tokens, 
-                   TokBuf parent, int parentOff) {
+                   TokBuf parent, int parentOff, ParseContext context) {
       _exprType = exprType;
       _tokens = tokens;
       _parent = parent;
       _parentOff = parentOff;
+      _context = context;
       if(parent == null) {
         // "top-level" expression, determine if it is a simple expression or not
         simpleExpr = isSimpleExpression();
@@ -897,6 +1079,10 @@ public class Expressionator
       return ExpressionTokenizer.newEntry(pos, toks);
     }
 
+    public Function getFunction(String funcName) {
+      return _context.getExpressionFunction(funcName);
+    }
+    
     @Override
     public String toString() {
       
@@ -958,6 +1144,30 @@ public class Expressionator
     }
   }
 
+  private static Value[] exprListToValues(
+      List<Expr> exprs, RowContext ctx) {
+    Value[] paramVals = new Value[exprs.size()];
+    for(int i = 0; i < exprs.size(); ++i) {
+      paramVals[i] = exprs.get(i).eval(ctx);
+    }
+    return paramVals;
+  }
+
+  private static Value[] exprListToDelayedValues(
+      List<Expr> exprs, RowContext ctx) {
+    Value[] paramVals = new Value[exprs.size()];
+    for(int i = 0; i < exprs.size(); ++i) {
+      paramVals[i] = new DelayedValue(exprs.get(i), ctx);
+    }
+    return paramVals;
+  }
+
+  private static void literalStrToString(String str, StringBuilder sb) {
+    sb.append("\"")
+      .append(str.replace("\"", "\"\""))
+      .append("\"");
+  }
+
   private static Pattern likePatternToRegex(String pattern, Object location) {
 
     StringBuilder sb = new StringBuilder(pattern.length());
@@ -1017,6 +1227,7 @@ public class Expressionator
                            Pattern.UNICODE_CASE);
   }
 
+
   private interface LeftAssocExpr {
     public OpType getOp();
     public Expr getLeft();
@@ -1029,22 +1240,63 @@ public class Expressionator
     public void setRight(Expr right);
   }
 
-  public static abstract class Expr
+  private static final class DelayedValue implements Value
+  {
+    private Value _val;
+    private final Expr _expr;
+    private final RowContext _ctx;
+
+    private DelayedValue(Expr expr, RowContext ctx) {
+      _expr = expr;
+      _ctx = ctx;
+    }
+
+    private Value getDelegate() {
+      if(_val == null) {
+        _val = _expr.eval(_ctx);
+      }
+      return _val;
+    }
+
+    public ValueType getType() {
+      return getDelegate().getType();
+    }
+
+    public Object get() {
+      return getDelegate().get();
+    }
+  }
+  
+
+  private static abstract class Expr implements Expression
   {
     public Object evalDefault() {
-      return eval(null);
+      Value val = eval(null);
+
+      if(val.getType() == ValueType.NULL) {
+        return null;
+      }
+
+      // FIXME, booleans seem to go to -1 (true),0 (false) ...?
+
+      return val.get();
     }
 
-    public boolean evalCondition(RowContext ctx) {
-      Object val = eval(ctx);
+    public Boolean evalCondition(RowContext ctx) {
+      Value val = eval(ctx);
+
+      if(val.getType() == ValueType.NULL) {
+        return null;
+      }
 
-      if(val instanceof Boolean) {
-        return (Boolean)val;
+      if(val.getType() != ValueType.BOOLEAN) {
+        // a single value as a conditional expression seems to act like an
+        // implicit "="
+        // FIXME, what about row validators?
+        val = BuiltinOperators.equals(val, ctx.getThisColumnValue());
       }
 
-      // a single value as a conditional expression seems to act like an
-      // implicit "="
-      return val.equals(ctx.getThisColumnValue());
+      return (Boolean)val.get();
     }
 
     @Override
@@ -1126,31 +1378,23 @@ public class Expressionator
       return outerExpr;
     }
     
-    protected abstract Object eval(RowContext ctx);
+    protected abstract Value eval(RowContext ctx);
 
     protected abstract void toExprString(StringBuilder sb, boolean isDebug);
   }
 
-  public interface RowContext
-  {
-    public Object getThisColumnValue();
-
-    public Object getRowValue(String collectionName, String objName,
-                              String colName);
-  }
-
   private static final class EConstValue extends Expr
   {
-    private final Object _val;
+    private final Value _val;
     private final String _str;
 
-    private EConstValue(Object val, String str) {
+    private EConstValue(Value val, String str) {
       _val = val;
       _str = str;
     }
 
     @Override 
-    protected Object eval(RowContext ctx) {
+    protected Value eval(RowContext ctx) {
       return _val;
     }
 
@@ -1162,28 +1406,28 @@ public class Expressionator
 
   private static final class ELiteralValue extends Expr
   {
-    private final Object _value;
+    private final Value _val;
 
-    private ELiteralValue(Object value) {
-      _value = value;
+    private ELiteralValue(ValueType valType, Object value) {
+      _val = new BuiltinOperators.SimpleValue(valType, value);
     }
 
     @Override
-    public Object eval(RowContext ctx) {
-      return _value;
+    public Value eval(RowContext ctx) {
+      return _val;
     }
 
     @Override
     protected void toExprString(StringBuilder sb, boolean isDebug) {
-      // FIXME, stronger typing?
-      if(_value instanceof String) {
-        sb.append("\"").append(_value).append("\"");
-      } else if(_value instanceof Date) {
-        // FIXME Date,Time,DateTime formatting?
-        sb.append("#").append(_value).append("#");
+      if(_val.getType() == ValueType.STRING) {
+        literalStrToString((String)_val.get(), sb);
+      } else if(_val.getType().isTemporal()) {
+        //   // FIXME Date,Time,DateTime formatting?
+        //   sb.append("#").append(_value).append("#");
+        throw new UnsupportedOperationException();
       } else {
-        sb.append(_value);
-      } 
+        sb.append(_val.get());
+      }
     }
   }
 
@@ -1201,7 +1445,7 @@ public class Expressionator
     }
 
     @Override
-    public Object eval(RowContext ctx) {
+    public Value eval(RowContext ctx) {
       return ctx.getRowValue(_collectionName, _objName, _fieldName);
     }
 
@@ -1217,16 +1461,6 @@ public class Expressionator
     }
   }
 
-  private static abstract class EOp
-  {
-    
-  }
-
-  private static abstract class ECond
-  {
-    
-  }
-
   private static class EParen extends Expr
   {
     private final Expr _expr;
@@ -1236,7 +1470,7 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
+    protected Value eval(RowContext ctx) {
       return _expr.eval(ctx);
     }
 
@@ -1250,24 +1484,22 @@ public class Expressionator
 
   private static class EFunc extends Expr
   {
-    private final String _name;
+    private final Function _func;
     private final List<Expr> _params;
 
-    private EFunc(String name, List<Expr> params) {
-      _name = name;
+    private EFunc(Function func, List<Expr> params) {
+      _func = func;
       _params = params;
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME how do func results act for conditional values? (literals become = tests)
-
-      return false;
+    protected Value eval(RowContext ctx) {
+      return _func.eval(exprListToValues(_params, ctx));
     }
 
     @Override
     protected void toExprString(StringBuilder sb, boolean isDebug) {
-      sb.append(_name).append("(");
+      sb.append(_func.getName()).append("(");
 
       if(!_params.isEmpty()) {
         exprListToString(_params, ",", sb, isDebug);
@@ -1320,16 +1552,13 @@ public class Expressionator
 
   private static class EBinaryOp extends EBaseBinaryOp
   {
-
     private EBinaryOp(BinaryOp op, Expr left, Expr right) {
       super(op, left, right);
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(RowContext ctx) {
+      return ((BinaryOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
     }
   }
 
@@ -1357,10 +1586,8 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(RowContext ctx) {
+      return ((UnaryOp)_op).eval(_expr.eval(ctx));
     }
 
     @Override
@@ -1380,10 +1607,8 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(RowContext ctx) {
+      return ((CompOp)_op).eval(_left.eval(ctx), _right.eval(ctx));
     }
   }
 
@@ -1394,10 +1619,12 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(final RowContext ctx) {
+      
+      // logical operations do short circuit evaluation, so we need to delay
+      // computing results until necessary
+      return ((LogOp)_op).eval(new DelayedValue(_left, ctx),
+                               new DelayedValue(_right, ctx));
     }
   } 
 
@@ -1432,10 +1659,8 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(RowContext ctx) {
+      return _op.eval(_expr.eval(ctx), null, null);
     }
 
     @Override
@@ -1447,6 +1672,7 @@ public class Expressionator
 
   private static class ELikeOp extends ESpecOp
   {
+    // FIXME, compile Pattern on first use?
     private final Pattern _pattern;
     private final String _patternStr;
 
@@ -1457,18 +1683,15 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(RowContext ctx) {
+      return _op.eval(_expr.eval(ctx), _pattern, null);
     }
 
     @Override
     protected void toExprString(StringBuilder sb, boolean isDebug) {
       _expr.toString(sb, isDebug);
-      sb.append(" ").append(_op).append(" \"")
-        .append(_patternStr.replace("\"", "\"\""))
-        .append("\"");
+      sb.append(" ").append(_op).append(" ");
+      literalStrToString(_patternStr, sb);
       if(isDebug) {
         sb.append("(").append(_pattern).append(")");
       }
@@ -1485,10 +1708,9 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(RowContext ctx) {
+      return _op.eval(_expr.eval(ctx), 
+                      exprListToDelayedValues(_exprs, ctx), null);
     }
 
     @Override
@@ -1522,10 +1744,10 @@ public class Expressionator
     }
 
     @Override
-    protected Object eval(RowContext ctx) {
-      // FIXME 
-
-      return null;
+    protected Value eval(RowContext ctx) {
+      return _op.eval(_expr.eval(ctx), 
+                      new DelayedValue(_startRangeExpr, ctx),
+                      new DelayedValue(_endRangeExpr, ctx));
     }
 
     @Override
index 039fb8a0f8356cc58a3980719feb1e3f0de9850f..8da921f0ac337ad661b878fdc2beae099d39bf5e 100644 (file)
@@ -40,7 +40,7 @@ public class ExpressionatorTest extends TestCase
 
     doTestSimpleBinOp("EBinaryOp", "+", "-", "*", "/", "\\", "^", "&", "Mod");
     doTestSimpleBinOp("ECompOp", "<", "<=", ">", ">=", "=", "<>");
-    doTestSimpleBinOp("ELogicalOp", "And", "Or", "Eqv", "Xor");
+    doTestSimpleBinOp("ELogicalOp", "And", "Or", "Eqv", "Xor", "Imp");
 
     for(String constStr : new String[]{"True", "False", "Null"}) {
       validateExpr(constStr, "<EConstValue>{" + constStr + "}");
@@ -120,7 +120,7 @@ public class ExpressionatorTest extends TestCase
 
   private static void validateExpr(String exprStr, String debugStr, 
                                    String cleanStr) {
-    Expressionator.Expr expr = Expressionator.parse(
+    Expression expr = Expressionator.parse(
         Expressionator.Type.FIELD_VALIDATOR, exprStr, null);
     assertEquals(debugStr, expr.toDebugString());
     assertEquals(cleanStr, expr.toString());