Permalink
Browse files

NReco lib: added LambdaParser.Eval overload that accepts get value de…

…legate + fixed issue with boolean operators and using bool constants as method parameters
  • Loading branch information...
VitaliyMF committed Jun 23, 2015
1 parent b028e14 commit 3ee3d5b67046dfa2e5a533cb4f065c53a85298ee
@@ -78,6 +78,9 @@ public class LambdaParserTests {
Assert.AreEqual("str", lambdaParser.Eval(" testObj.GetDelegNoParam()() ", varContext));
Assert.AreEqual("zzz", lambdaParser.Eval(" testObj.GetDelegOneParam()(\"zzz\") ", varContext));
Assert.AreEqual(false, lambdaParser.Eval("(testObj.FldTrue and false) || (testObj.FldTrue && false)", varContext ) );
Assert.AreEqual(true, lambdaParser.Eval("false or testObj.FldTrue", varContext ) );
Assert.AreEqual("True", lambdaParser.Eval("testObj.BoolParam(true)", varContext ) );
}
[Test]
@@ -120,6 +123,10 @@ public class TestClass {
return String.Format(s, arg1, arg2);
}
public string BoolParam(bool flag) {
return flag.ToString();
}
public Func<string, string> GetDelegOneParam() {
return (s) => {
return s;
@@ -48,6 +48,14 @@ internal sealed class LambdaParameterWrapper : IComparable {
return ValueComparer.Instance.Compare(Value, objResolved);
}
public bool IsTrue {
get {
if (_Value==null)
return false;
return _Value is bool ? (bool)_Value : ConvertManager.ChangeType<bool>(_Value);
}
}
public static LambdaParameterWrapper CreateDictionary(object[] keys, object[] values) {
if (keys.Length!=values.Length)
throw new ArgumentException();
@@ -236,6 +244,31 @@ internal sealed class LambdaParameterWrapper : IComparable {
return compareRes <= 0;
}
public static LambdaParameterWrapper operator &(LambdaParameterWrapper c1, LambdaParameterWrapper c2) {
return new LambdaParameterWrapper( c1.IsTrue && c2.IsTrue );
}
public static LambdaParameterWrapper operator &(LambdaParameterWrapper c1, bool c2) {
return new LambdaParameterWrapper( c1.IsTrue && c2 );
}
public static bool operator &(bool c1, LambdaParameterWrapper c2) {
return c1 && c2.IsTrue;
}
public static bool operator true(LambdaParameterWrapper x) {
return x.IsTrue;
}
public static LambdaParameterWrapper operator |(LambdaParameterWrapper c1, LambdaParameterWrapper c2) {
return new LambdaParameterWrapper( c1.IsTrue || ConvertManager.ChangeType<bool>(c2.Value) );
}
public static LambdaParameterWrapper operator |(LambdaParameterWrapper c1, bool c2) {
return new LambdaParameterWrapper( c1.IsTrue || c2 );
}
public static bool operator |(bool c1, LambdaParameterWrapper c2) {
return c1 || c2.IsTrue;
}
public static bool operator false(LambdaParameterWrapper x) {
return !x.IsTrue;
}
}
@@ -39,8 +39,10 @@ public class LambdaParser {
static IDictionary<string, CompiledExpression> CachedExpressions = new Dictionary<string, CompiledExpression>();
public LambdaParser() {
public bool UseCache { get; set; }
public LambdaParser() {
UseCache = true;
}
internal class ExtractParamsVisitor : ExpressionVisitor {
@@ -56,26 +58,39 @@ internal class ExtractParamsVisitor : ExpressionVisitor {
}
}
public static ParameterExpression[] GetExpressionParameters(Expression expr) {
var paramsVisitor = new ExtractParamsVisitor();
paramsVisitor.Visit(expr);
return paramsVisitor.ParamsList.ToArray();
}
public object Eval(string expr, IDictionary<string, object> vars) {
CompiledExpression compiledExpr;
if (!CachedExpressions.TryGetValue(expr, out compiledExpr)) {
var linqExpr = Parse(expr, vars);
var paramsVisitor = new ExtractParamsVisitor();
paramsVisitor.Visit(linqExpr);
return Eval(expr, (varName) => {
object val = null;
vars.TryGetValue(varName, out val);
return val;
});
}
public object Eval(string expr, Func<string,object> getVarValue) {
CompiledExpression compiledExpr;
if (!UseCache || !CachedExpressions.TryGetValue(expr, out compiledExpr)) {
var linqExpr = Parse(expr);
compiledExpr = new CompiledExpression() {
Parameters = paramsVisitor.ParamsList.ToArray()
Parameters = GetExpressionParameters(linqExpr)
};
var lambdaExpr = Expression.Lambda(linqExpr, compiledExpr.Parameters);
compiledExpr.Lambda = lambdaExpr.Compile();
lock (CachedExpressions) {
CachedExpressions[expr] = compiledExpr;
}
if (UseCache)
lock (CachedExpressions) {
CachedExpressions[expr] = compiledExpr;
}
}
var valuesList = new List<object>();
foreach (var paramExpr in compiledExpr.Parameters) {
valuesList.Add( new LambdaParameterWrapper( vars.ContainsKey(paramExpr.Name) ? vars[paramExpr.Name] : null ) );
valuesList.Add( new LambdaParameterWrapper( getVarValue(paramExpr.Name)) );
}
var lambdaRes = compiledExpr.Lambda.DynamicInvoke(valuesList.ToArray());
@@ -85,16 +100,14 @@ internal class ExtractParamsVisitor : ExpressionVisitor {
}
public Expression Parse(string expr, IDictionary<string, object> vars) {
public Expression Parse(string expr) {
var parseResult = ParseConditional(expr, 0);
var lastLexem = ReadLexem(expr, parseResult.End);
if (lastLexem.Type != LexemType.Stop)
throw new LambdaParserException(expr, parseResult.End, "Invalid expression");
return parseResult.Expr;
}
protected Lexem ReadLexem(string s, int startIdx) {
var lexem = new Lexem();
lexem.Type = LexemType.Unknown;
@@ -173,7 +186,7 @@ internal class ExtractParamsVisitor : ExpressionVisitor {
var negativeOpExpr = Expression.New(LambdaParameterWrapperConstructor, Expression.Convert( negativeOp.Expr, typeof(object)));
return new ParseResult() {
End = negativeOp.End,
Expr = Expression.Condition(testExpr.Expr, positiveOpExpr, negativeOpExpr)
Expr = Expression.Condition( Expression.IsTrue( testExpr.Expr ), positiveOpExpr, negativeOpExpr)
};
} else {
@@ -484,9 +497,9 @@ internal class ExtractParamsVisitor : ExpressionVisitor {
var val = lexem.GetValue();
switch (val) {
case "true":
return new ParseResult() { End = lexem.End, Expr = Expression.Constant(true) };
return new ParseResult() { End = lexem.End, Expr = Expression.Constant(new LambdaParameterWrapper(true) ) };
case "false":
return new ParseResult() { End = lexem.End, Expr = Expression.Constant(false) };
return new ParseResult() { End = lexem.End, Expr = Expression.Constant(new LambdaParameterWrapper(false) ) };
case "new":
return ReadNewInstance(expr, lexem.End);
}
@@ -31,5 +31,5 @@
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("2.0.2")]
[assembly: AssemblyFileVersion("2.0.2")]
[assembly: AssemblyVersion("2.0.3")]
[assembly: AssemblyFileVersion("2.0.3")]

0 comments on commit 3ee3d5b

Please sign in to comment.