From 32fc3f80769785495a3474654528529c2d874c02 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Thu, 17 Aug 2017 02:41:12 +0000 Subject: [PATCH] implement various functions git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/branches/exprs@1112 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../jackcess/impl/expr/BuiltinOperators.java | 1 + .../jackcess/impl/expr/DefaultFunctions.java | 241 +++++++++++++++++- 2 files changed, 238 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java index 5a3d7b5..93b4bf2 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/BuiltinOperators.java @@ -51,6 +51,7 @@ public class BuiltinOperators public static final Value TRUE_VAL = new LongValue(-1L); public static final Value FALSE_VAL = new LongValue(0L); public static final Value EMPTY_STR_VAL = new StringValue(""); + public static final Value ZERO_VAL = FALSE_VAL; private enum CoercionType { SIMPLE(true, true), GENERAL(false, true), COMPARE(false, false); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java index fbcd683..49fecb7 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/expr/DefaultFunctions.java @@ -16,13 +16,14 @@ limitations under the License. package com.healthmarketscience.jackcess.impl.expr; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.HashMap; import java.util.Map; - -import com.healthmarketscience.jackcess.expr.Value; -import com.healthmarketscience.jackcess.expr.Function; import com.healthmarketscience.jackcess.expr.EvalContext; +import com.healthmarketscience.jackcess.expr.Function; +import com.healthmarketscience.jackcess.expr.Value; /** * @@ -33,6 +34,8 @@ public class DefaultFunctions private static final Map FUNCS = new HashMap(); + private static final char NON_VAR_SUFFIX = '$'; + private DefaultFunctions() {} public static Function getFunction(String name) { @@ -78,6 +81,20 @@ public class DefaultFunctions } } + public static abstract class Func0 extends BaseFunction + { + protected Func0(String name) { + super(name, 0, 9); + } + + public final Value eval(EvalContext ctx, Value... params) { + validateNumParams(params); + return eval0(ctx); + } + + protected abstract Value eval0(EvalContext ctx); + } + public static abstract class Func1 extends BaseFunction { protected Func1(String name) { @@ -130,15 +147,231 @@ public class DefaultFunctions } }); + public static final Function ASC = registerFunc(new Func1("Asc") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + String str = param1.getAsString(); + int len = str.length(); + if(len == 0) { + throw new IllegalStateException("No characters in string"); + } + long lv = str.charAt(0); + if((lv < 0) || (lv > 255)) { + throw new IllegalStateException("Character code '" + lv + + "' out of range "); + } + return BuiltinOperators.toValue(lv); + } + }); + + public static final Function ASCW = registerFunc(new Func1("AscW") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + String str = param1.getAsString(); + int len = str.length(); + if(len == 0) { + throw new IllegalStateException("No characters in string"); + } + long lv = str.charAt(0); + return BuiltinOperators.toValue(lv); + } + }); + + public static final Function CHR = registerStringFunc(new Func1("Chr") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + long lv = param1.getAsLong(); + if((lv < 0) || (lv > 255)) { + throw new IllegalStateException("Character code '" + lv + + "' out of range "); + } + char[] cs = Character.toChars((int)lv); + return BuiltinOperators.toValue(new String(cs)); + } + }); + + public static final Function CHRW = registerStringFunc(new Func1("ChrW") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + long lv = param1.getAsLong(); + char[] cs = Character.toChars((int)lv); + return BuiltinOperators.toValue(new String(cs)); + } + }); + + public static final Function HEX = registerStringFunc(new Func1("Hex") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + if(param1.isNull()) { + return param1; + } + if((param1.getType() == Value.Type.STRING) && + (param1.getAsString().length() == 0)) { + return BuiltinOperators.ZERO_VAL; + } + long lv = param1.getAsLong(); + return BuiltinOperators.toValue(Long.toHexString(lv)); + } + }); + + public static final Function NZ = registerFunc(new BaseFunction("Nz", 1, 2) { + public Value eval(EvalContext ctx, Value... params) { + validateNumParams(params); + Value param1 = params[0]; + if(!param1.isNull()) { + return param1; + } + if(params.length > 1) { + return params[1]; + } + Value.Type resultType = ctx.getResultType(); + return (((resultType == null) || + (resultType == Value.Type.STRING)) ? + BuiltinOperators.EMPTY_STR_VAL : BuiltinOperators.ZERO_VAL); + } + }); + + public static final Function OCT = registerStringFunc(new Func1("Oct") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + if(param1.isNull()) { + return param1; + } + if((param1.getType() == Value.Type.STRING) && + (param1.getAsString().length() == 0)) { + return BuiltinOperators.ZERO_VAL; + } + long lv = param1.getAsLong(); + return BuiltinOperators.toValue(Long.toOctalString(lv)); + } + }); + + public static final Function STR = registerStringFunc(new Func1("Str") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + BigDecimal bd = param1.getAsBigDecimal(); + String str = bd.toPlainString(); + if(bd.compareTo(BigDecimal.ZERO) >= 0) { + str = " " + str; + } + return BuiltinOperators.toValue(str); + } + }); + + public static final Function CBOOL = registerFunc(new Func1("CBool") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + boolean b = param1.getAsBoolean(); + return BuiltinOperators.toValue(b); + } + }); + + public static final Function CBYTE = registerFunc(new Func1("CByte") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + Long lv = param1.getAsLong(); + if((lv < 0) || (lv > 255)) { + throw new IllegalStateException("Byte code '" + lv + "' out of range "); + } + return BuiltinOperators.toValue(lv); + } + }); + + public static final Function CCUR = registerFunc(new Func1("CCur") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + BigDecimal bd = param1.getAsBigDecimal(); + bd.setScale(4, RoundingMode.HALF_EVEN); + return BuiltinOperators.toValue(bd); + } + }); + + // public static final Function CDATE = registerFunc(new Func1("CDate") { + // @Override + // protected Value eval1(EvalContext ctx, Value param1) { + // FIXME + // BigDecimal bd = param1.getAsBigDecimal(); + // bd.setScale(4, RoundingMode.HALF_EVEN); + // return BuiltinOperators.toValue(bd); + // } + // }); + + public static final Function CDBL = registerFunc(new Func1("CDbl") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + Double db = param1.getAsDouble(); + return BuiltinOperators.toValue(db); + } + }); + + public static final Function CDEC = registerFunc(new Func1("CDec") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + BigDecimal bd = param1.getAsBigDecimal(); + return BuiltinOperators.toValue(bd); + } + }); + + public static final Function CINT = registerFunc(new Func1("CInt") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + // FIXME, fix rounding for this and clng + Long lv = param1.getAsLong(); + if((lv < Short.MIN_VALUE) || (lv > Short.MAX_VALUE)) { + throw new IllegalStateException("Int value '" + lv + "' out of range "); + } + return BuiltinOperators.toValue(lv); + } + }); + + public static final Function CLNG = registerFunc(new Func1("CLng") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + Long lv = param1.getAsLong(); + if((lv < Integer.MIN_VALUE) || (lv > Integer.MAX_VALUE)) { + throw new IllegalStateException("Long value '" + lv + "' out of range "); + } + return BuiltinOperators.toValue(lv); + } + }); + + public static final Function CSNG = registerFunc(new Func1("CSng") { + @Override + protected Value eval1(EvalContext ctx, Value param1) { + Double db = param1.getAsDouble(); + if((db < Float.MIN_VALUE) || (db > Float.MAX_VALUE)) { + throw new IllegalStateException("Single value '" + db + "' out of range "); + } + return BuiltinOperators.toValue(db); + } + }); + + // FIXME, CSTR, CVAR // https://www.techonthenet.com/access/functions/ // https://support.office.com/en-us/article/Access-Functions-by-category-b8b136c3-2716-4d39-94a2-658ce330ed83 private static Function registerFunc(Function func) { - if(FUNCS.put(func.getName().toLowerCase(), func) != null) { + return registerFunc(false, func); + } + + private static Function registerStringFunc(Function func) { + return registerFunc(true, func); + } + + private static Function registerFunc(boolean includeNonVar, Function func) { + String fname = func.getName().toLowerCase(); + if(FUNCS.put(fname, func) != null) { throw new IllegalStateException("Duplicate function " + func); } + if(includeNonVar) { + // for our purposes the non-variant versions are the same function + fname += NON_VAR_SUFFIX; + if(FUNCS.put(fname, func) != null) { + throw new IllegalStateException("Duplicate function " + func); + } + } return func; } } -- 2.39.5