Skip to content

Commit

Permalink
Fix for array indexers
Browse files Browse the repository at this point in the history
Fix for implicit conversion for binary operator
Fix for implicit null literal conversion
  • Loading branch information
RupertAvery committed Mar 6, 2015
1 parent 4cdbec0 commit 3163a03
Show file tree
Hide file tree
Showing 24 changed files with 559 additions and 360 deletions.
1 change: 1 addition & 0 deletions ExpressionEvaluator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
<Compile Include="CompiledExpression.cs" />
<Compile Include="CompiledExpressionType.cs" />
<Compile Include="ExpressionCompiler.cs" />
<Compile Include="IDynamicObjectProvider.cs" />
<Compile Include="ParseException.cs" />
<Compile Include="Parser\AntlrParser.cs" />
<Compile Include="Parser\CompilerState.cs" />
Expand Down
13 changes: 13 additions & 0 deletions IDynamicObjectProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ExpressionEvaluator
{
public interface IDynamicObjectProvider
{
void setVar(string propertyname, object value);
object getVar(string propertyname);
}
}
19 changes: 16 additions & 3 deletions Parser/ExpressionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,21 @@ public static Expression GetPropertyIndex(Expression le, IEnumerable<Expression>
}
else
{
if (type.BaseType == typeof(System.Array))
{
return Expression.ArrayAccess(le, args);
}

var interfaces = le.Type.GetInterfaces();

if (interfaces.Any(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IList<>)))
{
var indexer = le.Type.GetProperties().Single(x => x.Name == "Item");
var indexer = le.Type.GetProperties().SingleOrDefault(x => x.Name == "Item");
if (indexer == null)
{
var me = ((MemberExpression)le);
throw new CompilerException(string.Format("The member '{0}' does not implement the index accessor", me.Member.Name));
}
return Expression.Property(le, indexer, args);
}
return Expression.ArrayAccess(le, args);
Expand Down Expand Up @@ -747,8 +757,11 @@ public static Expression BinaryOperator(Expression le, Expression re, Expression
}
else
{
re = TypeConversion.ImplicitConversion(le, re);
le = TypeConversion.DynamicConversion(re, le);
var ore = re;
var ole = le;
re = TypeConversion.ImplicitConversion(ole, ore);
le = TypeConversion.ImplicitConversion(ore, ole);
//le = TypeConversion.DynamicConversion(re, le);
return GetBinaryOperator(le, re, expressionType);
}
}
Expand Down
9 changes: 9 additions & 0 deletions Parser/ExpressionParseException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,13 @@ public ExpressionParseException(string message, ITokenStream tokenStream)
this._tokenStream = tokenStream;
}
}

[Serializable]
public class CompilerException : Exception
{
public CompilerException(string message)
: base(message)
{
}
}
}
18 changes: 13 additions & 5 deletions Parser/TypeConversion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,21 @@ public static Expression ReferenceConversion(Expression dest, Expression src)
return src;
}

public static bool IsNullableType(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof (Nullable<>);
}

// 6.1.7 Boxing Conversions
// A boxing conversion permits a value-type to be implicitly converted to a reference type. A boxing conversion exists from any non-nullable-value-type to object and dynamic, to System.ValueType and to any interface-type implemented by the non-nullable-value-type. Furthermore an enum-type can be converted to the type System.Enum.
// A boxing conversion permits a value-type to be implicitly converted to a reference type. A boxing conversion exists from any non-nullable-value-type to object and dynamic,
// to System.ValueType and to any interface-type implemented by the non-nullable-value-type.
// Furthermore an enum-type can be converted to the type System.Enum.
// A boxing conversion exists from a nullable-type to a reference type, if and only if a boxing conversion exists from the underlying non-nullable-value-type to the reference type.
// A value type has a boxing conversion to an interface type I if it has a boxing conversion to an interface type I0 and I0 has an identity conversion to I.

public static Expression BoxingConversion(Expression dest, Expression src)
{
if (src.Type.IsValueType && dest.Type.IsDynamicOrObject())
if (src.Type.IsValueType && !IsNullableType(src.Type) && dest.Type.IsDynamicOrObject())
{
src = Expression.Convert(src, dest.Type);
}
Expand All @@ -166,11 +173,12 @@ public static Expression NullableConverion(Expression dest, Expression src)
}


// 6.1.5 Null literal conversions
//An implicit conversion exists from the null literal to any nullable type. This conversion produces the null value (§4.1.10) of the given nullable type.
// 6.1.5 Null literal conversions
// An implicit conversion exists from the null literal to any nullable type.
// This conversion produces the null value (§4.1.10) of the given nullable type.
public static Expression NullLiteralConverion(Expression dest, Expression src)
{
if (src.NodeType == ExpressionType.Constant && src.Type == typeof(object) && ((ConstantExpression)src).Value == null && Nullable.GetUnderlyingType(dest.Type) != null)
if (src.NodeType == ExpressionType.Constant && src.Type == typeof(object) && ((ConstantExpression)src).Value == null && IsNullableType(dest.Type))
{
return Expression.Constant(Activator.CreateInstance(dest.Type), dest.Type);
}
Expand Down
4 changes: 2 additions & 2 deletions Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.0.2.0")]
[assembly: AssemblyFileVersion("2.0.2.0")]
[assembly: AssemblyVersion("2.0.2.2")]
[assembly: AssemblyFileVersion("2.0.2.2")]

[assembly: AllowPartiallyTrustedCallers]
[assembly: SecurityTransparent]
Expand Down
66 changes: 64 additions & 2 deletions TestProject1/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public void UnavailablePropertyThrowsException()
catch (ParseException exception)
{
var regex = new Regex("Cannot resolve member \"(\\w\\S+)\" on type \"(\\w\\S+)\"");
var m = regex.Match(((ExpressionParseException) exception.InnerException).Message);
var m = regex.Match(((ExpressionParseException)exception.InnerException).Message);
Assert.AreEqual(m.Groups[1].Value, "unavailableProperty");
Assert.AreEqual(m.Groups[2].Value, "Helper");
}
Expand All @@ -144,7 +144,7 @@ public void ScopeCompile()
{
var helper = new Helper();
var str = "availableMethod(1)";
var c = new CompiledExpression(str) { TypeRegistry = new TypeRegistry()};
var c = new CompiledExpression(str) { TypeRegistry = new TypeRegistry() };
var ret = c.ScopeCompile<Helper>();
ret(helper);
}
Expand Down Expand Up @@ -197,8 +197,14 @@ public void ImplicitNumericCasting()
var str = "2.5D + 1";
var c = new CompiledExpression(str);
var ret = c.Eval();

Assert.IsTrue(ret.GetType() == typeof(System.Double));
Assert.IsTrue(Convert.ToDouble(ret) == 3.5D);

c.StringToParse = "1 + 2.5d";
ret = c.Eval();
Assert.IsTrue(ret.GetType() == typeof(System.Double));
Assert.IsTrue(Convert.ToDouble(ret) == 3.5d);
}

public class Container
Expand Down Expand Up @@ -422,6 +428,16 @@ public void SwitchStatement()
}
}

[TestMethod]
public void MixedNumericTypes()
{
var reg = new TypeRegistry();
reg.RegisterType("Math", typeof(Math));
var exp = "(1*2) + (0.8324057*1)";
var expression = new CompiledExpression(exp) { TypeRegistry = reg };
var value = expression.Eval();
}

//[TestMethod]
//public void Return()
//{
Expand Down Expand Up @@ -868,6 +884,12 @@ public void NullableType()
expression.TypeRegistry.RegisterSymbol("Argument1", argument1, typeof(int?));
expression.TypeRegistry.RegisterSymbol("Argument2", argument2);

var x = argument2.Count != null;
var y = null != argument2.Count;

expression.StringToParse = "null != Argument2.Count";
expression.Eval();

// Works
expression.StringToParse = "Argument2.Count != null";
expression.Eval();
Expand Down Expand Up @@ -965,6 +987,45 @@ public void ExpressionException3()
c.StringToParse = "25.L";
var result = c.Eval();
}

[TestMethod]
public void ListIndexers()
{
var a = new MyClass() { Y = new List<int>() { 1, 45, 88, 22 }, Z = new[] { 7, 11, 33, 65 } };
var t = new TypeRegistry();
t.RegisterSymbol("a", a);
var c = new CompiledExpression() { TypeRegistry = t };

// Access List item by index
c.StringToParse = "a.Y[3]";
var result = c.Eval();
Assert.AreEqual(result, 22);

// Access array item by index
c.StringToParse = "a.Z[1]";
result = c.Eval();
Assert.AreEqual(result, 11);
}

[TestMethod]
public void ExpandoObjects()
{
dynamic A = new ExpandoObject();
dynamic B = new ExpandoObject();
A.Num1 = 1000;
B.Num2 = 50;

var t = new TypeRegistry();
t.RegisterSymbol("A", A);
t.RegisterSymbol("B", B);
var c = new CompiledExpression() { TypeRegistry = t };
c.StringToParse = "A.Num1 - B.Num2";
var result = c.Eval();
Assert.AreEqual(result, 950);

}


}

public class Fact
Expand All @@ -976,6 +1037,7 @@ public class MyClass
{
public int X { get; set; }
public List<int> Y { get; set; }
public int[] Z { get; set; }
public Func<bool> Value { get; set; }
public void Foo()
{
Expand Down
7 changes: 7 additions & 0 deletions Tests/IImportedValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Tests
{
public interface IImportedValue
{

}
}
7 changes: 7 additions & 0 deletions Tests/ImportedValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Tests
{
public class ImportedValue : IImportedValue
{

}
}
11 changes: 11 additions & 0 deletions Tests/Indexers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.Generic;

namespace Tests
{
public class Indexers
{
public List<int> a { get; set; }
public IEnumerable<int> b { get; set; }
public int[] c { get; set; }
}
}
26 changes: 26 additions & 0 deletions Tests/MyClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;

namespace Tests
{
public class MyClass
{
public int X { get; set; }
public List<int> Y { get; set; }
public Func<bool> Value { get; set; }
public void Foo()
{
X++;
}

public void Foo(int value)
{
X += value;
}

public int Bar(int value)
{
return value * 2;
}
}
}
9 changes: 9 additions & 0 deletions Tests/NumEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Tests
{
public enum NumEnum
{
One = 1,
Two = 2,
Three = 3
}
}
Loading

0 comments on commit 3163a03

Please sign in to comment.