private static Expression PredefinedAtom_Convert(ParseTreeNode root, CompilerState state) { root.RequireChildren(2); var arg1Node = root.RequireChild(null, 1, 0, 0); var value = state.ParentRuntime.Analyze(arg1Node, state).RemoveNullability(); var arg2Node = root.RequireChild("string", 1, 0, 1); var targetType = RequireSystemType(arg2Node); // maybe we don't have to change type, or simply cast the numeric type? Expression adjusted; if (ExpressionTreeExtensions.TryAdjustReturnType(root, value, targetType, out adjusted)) { return(adjusted); } if (value.IsString()) { // or maybe they are asking to convert Base64 string into binary value? if (targetType.IsBinary()) { return(ConstantHelper.TryEvalConst(root, targetType.GetConstructor(new[] { typeof(string) }), value)); } // maybe we can parse string to a number? if ((targetType.IsNumeric() || targetType.IsDateTime() || targetType.IsTimeSpan() || targetType.IsGuid())) { var parseMethod = ReflectionHelper.GetOrAddMethod1(targetType, "Parse", value.Type); return(ConstantHelper.TryEvalConst(root, parseMethod, value)); } } // maybe we can generate a string from a number or other object? if (targetType.IsString()) { if (value.IsBinary()) { var toStringMethod = ReflectionHelper.GetOrAddMethod1(value.Type, "ToBase64String", value.Type); return(ConstantHelper.TryEvalConst(root, toStringMethod, value)); } else { var toStringMethod = ReflectionHelper.GetOrAddMethod0(value.Type, "ToString"); return(ConstantHelper.TryEvalConst(root, toStringMethod, value)); } } // seems like cast does not apply, let's use converter try { var convertMethod = ReflectionHelper.GetOrAddMethod1(typeof(Convert), "To" + targetType.Name, value.Type); return(ConstantHelper.TryEvalConst(root, convertMethod, value)); } catch { throw new CompilationException(string.Format("There is no conversion from type {0} to type {1}", value.Type.FullName, targetType.FullName), arg2Node); } }
private static Expression PredefinedAtom_StringLike(ParseTreeNode root, string methodName, CompilerState state) { Expression value; Expression pattern; var method = PrepareStringInstanceMethodCall(methodName, ExpressionTreeExtensions.UnwindTupleExprList(root), state, out value, out pattern); var constValue = value as ConstantExpression; var constPattern = pattern as ConstantExpression; if (!ReferenceEquals(constValue, null) && !ReferenceEquals(constPattern, null)) { return(constValue.Value == null || constPattern.Value == null ? Expression.Constant(false) : ConstantHelper.TryEvalConst(root, method, constValue, constPattern, Expression.Constant(StringComparison.OrdinalIgnoreCase))); } Expression target = Expression.Call(value, method, pattern, Expression.Constant(StringComparison.OrdinalIgnoreCase)); if (target.Type == typeof(Int32)) { target = Expression.GreaterThanOrEqual(target, Expression.Constant(0, target.Type)); } return(Expression.Condition( Expression.ReferenceEqual(Expression.Constant(null), value), Expression.Constant(false), Expression.Condition( Expression.ReferenceEqual(Expression.Constant(null), pattern), Expression.Constant(false), target))); }
private static Expression PredefinedAtom_ToDateTime(ParseTreeNode root, CompilerState state) { root.RequireChildren(2); var argNodes = root.RequireChild(null, 1, 0); if (argNodes.ChildNodes.Count == 3 || argNodes.ChildNodes.Count == 6 || argNodes.ChildNodes.Count == 7) { var args = new Expression[argNodes.ChildNodes.Count]; var typeArray = new Type[args.Length]; var typeInt32 = typeof(Int32); for (var i = 0; i < args.Length; i++) { var node = argNodes.RequireChild(null, i); var expr = ExpressionTreeExtensions.AdjustReturnType(node, state.ParentRuntime.Analyze(node, state), typeInt32); args[i] = expr; typeArray[i] = typeInt32; } var ctr = typeof(DateTime).GetConstructor(typeArray); if (ctr == null) { throw new Exception(string.Format("Could not locate datetime constructor with {0} int arguments", typeArray.Length)); } return(ConstantHelper.TryEvalConst(root, ctr, args)); } if (argNodes.ChildNodes.Count == 2) { var arg1Node = root.RequireChild(null, 1, 0, 0); var valueString = state.ParentRuntime.Analyze(arg1Node, state).RemoveNullability(); valueString.RequireString(arg1Node); var arg2Node = root.RequireChild(null, 1, 0, 1); var formatString = state.ParentRuntime.Analyze(arg2Node, state).RemoveNullability(); formatString.RequireString(arg2Node); return(ConstantHelper.TryEvalConst(root, ReflectionHelper.DateTimeParseExact, valueString, formatString, Expression.Constant(null, typeof(IFormatProvider)))); } if (argNodes.ChildNodes.Count == 1) { var arg1Node = root.RequireChild(null, 1, 0, 0); var value = state.ParentRuntime.Analyze(arg1Node, state).RemoveNullability(); value.RequireInteger(arg1Node); var dateTimeFromBinary = ReflectionHelper.GetOrAddMethod1(typeof(DateTime), "FromBinary", typeof(Int64)); return(ConstantHelper.TryEvalConst(root, dateTimeFromBinary, value)); } throw new CompilationException("ToDateTime has four overloads: with two, three, six and seven arguments", root); }
private Expression BuildBetweenExpression(ParseTreeNode root, CompilerState state) { root.RequireChildren(6); var variableNode = root.ChildNodes[0]; var notOpt = root.RequireChild("notOpt", 1); root.RequireChild("between", 2); root.RequireChild("and", 4); var argument = Analyze(variableNode, state).RemoveNullability(); var leftNode = root.ChildNodes[3]; var leftExpr = Analyze(leftNode, state); leftExpr.RequireNonVoid(leftNode); leftExpr = ExpressionTreeExtensions.AdjustReturnType(leftNode, leftExpr, argument.Type); var rightNode = root.ChildNodes[5]; var rightExpr = Analyze(rightNode, state); rightExpr.RequireNonVoid(rightNode); rightExpr = ExpressionTreeExtensions.AdjustReturnType(rightNode, rightExpr, argument.Type); Expression lower, upper; if (argument.IsString()) { lower = ConstantHelper.TryEvalConst(leftNode, PrepareStringComparison(leftNode, leftExpr, argument), Expression.Constant(0), ExpressionType.LessThanOrEqual); upper = ConstantHelper.TryEvalConst(rightNode, PrepareStringComparison(rightNode, rightExpr, argument), Expression.Constant(0), ExpressionType.GreaterThanOrEqual); } else { lower = ConstantHelper.TryEvalConst(leftNode, argument, leftExpr, ExpressionType.GreaterThanOrEqual); upper = ConstantHelper.TryEvalConst(rightNode, argument, rightExpr, ExpressionType.LessThanOrEqual); } var result = ConstantHelper.TryEvalConst(root, lower, upper, ExpressionType.AndAlso); if (notOpt.ChildNodes.Count > 0) { result = ConstantHelper.TryEvalConst(root, result, ExpressionType.Not); } return(result); }
private static Expression PredefinedAtom_Cast(ParseTreeNode root, CompilerState state) { root.RequireChildren(2); var arg1Node = root.RequireChild(null, 1, 0, 0); var value = state.ParentRuntime.Analyze(arg1Node, state).RemoveNullability(); var arg2Node = root.RequireChild("string", 1, 0, 1); var targetType = RequireSystemType(arg2Node); if (value.IsVoid()) { return(ExpressionTreeExtensions.GetDefaultExpression(targetType)); } // bluntly attempt to convert the type; will throw if types are not compatible return(ConstantHelper.TryEvalConst(root, value, ExpressionType.Convert, targetType)); }
private static Expression PredefinedAtom_IsDefault(ParseTreeNode root, CompilerState state) { var funArgs = ExpressionTreeExtensions.UnwindTupleExprList(root.RequireChild("exprList", 1, 0)); funArgs.RequireChildren(1); var arg1Node = funArgs.ChildNodes[0]; var value = state.ParentRuntime.Analyze(arg1Node, state).RemoveNullability(); if (value.IsString()) { if (value is ConstantExpression) { return(Expression.Constant(string.IsNullOrEmpty((string)((ConstantExpression)value).Value), typeof(Boolean))); } return(Expression.Call(ReflectionHelper.StringIsNullOrEmpty, value)); } return(ConstantHelper.TryEvalConst(root, value, Expression.Default(value.Type), ExpressionType.Equal)); }
private Expression BuildIsNullPredicate(ParseTreeNode root, CompilerState state, bool compareIsNull) { var value = state.ParentRuntime.Analyze(root, state); // IS NULL if (compareIsNull) { return(value.Type.IsNullableType() ? ConstantHelper.TryEvalConst(root, ConstantHelper.TryEvalConst(root, value.Type.GetField("HasValue"), value), ExpressionType.Not, typeof(bool)) : value.Type.IsValueType ? Expression.Constant(false) : ConstantHelper.TryEvalConst(root, value, Expression.Constant(null), ExpressionType.Equal)); } // IS NOT NULL return(value.Type.IsNullableType() ? ConstantHelper.TryEvalConst(root, value.Type.GetField("HasValue"), value) : value.Type.IsValueType ? Expression.Constant(true) : ConstantHelper.TryEvalConst(root, value, Expression.Constant(null), ExpressionType.NotEqual)); }
private Expression BuildInclusionExpression(ParseTreeNode root, Expression leftExpr, string op, CompilerState state) { root.RequireChildren(3); var rightNodeList = ExpressionTreeExtensions.UnwindTupleExprList(root.ChildNodes[2]); if (rightNodeList.Term.Name == "Id") { throw new CompilationException("Parameterized IN statement is not yet supported, consider using function SetContains", rightNodeList); } if (rightNodeList.Term.Name != "exprList") { throw new CompilationException("Argument for IN operator must be a list of expressions", root); } // Expression text is not supposed to be used to pass tens and hundreds of thousands of IDs in plain text. // Use parameters for large argument sets. rightNodeList.RequireChildren(1, 1000); leftExpr = leftExpr.RemoveNullability(); // compile a method to enumerate values in the argument set var valueEnumerator = ReflectionHelper.EnumerateValues.MakeGenericMethod(leftExpr.Type); // invoke enumerator, output is a hashset object matchingSet; try { matchingSet = valueEnumerator.Invoke(null, new object[] { this, rightNodeList, state }); } catch (TargetInvocationException e) { if (e.InnerException == null) { throw; } throw e.InnerException; } // how many items do we have there? var countProperty = matchingSet.GetType().GetProperty("Count", BindingFlags.Instance | BindingFlags.Public); var count = (int)(countProperty.GetValue(matchingSet)); Expression contains; var leftArgConst = leftExpr as ConstantExpression; if (leftArgConst != null) { // since list is constant and argument is constant, let's just evaluate it var setContainsMethod = ReflectionHelper.GetOrAddMethod1(matchingSet.GetType(), "Contains", leftExpr.Type); try { contains = Expression.Constant(setContainsMethod.Invoke(matchingSet, new[] { leftArgConst.Value }), typeof(bool)); } catch (TargetInvocationException e) { if (e.InnerException == null) { throw; } throw e.InnerException; } } else { var threshold = leftExpr.IsInteger() ? 15 : 5; // for small sets of values, just create a chain of IF/THEN/ELSE statements if (count <= threshold) { var isString = leftExpr.IsString(); var enumeratorMethod = ReflectionHelper.GetOrAddMethod0(matchingSet.GetType(), "GetEnumerator"); IEnumerator enumerator; try { enumerator = (IEnumerator)enumeratorMethod.Invoke(matchingSet, null); } catch (TargetInvocationException e) { if (e.InnerException == null) { throw; } throw e.InnerException; } contains = null; while (enumerator.MoveNext()) { var next = isString ? PrepareStringEquality(rightNodeList, leftExpr, Expression.Constant(enumerator.Current, leftExpr.Type)) : Expression.Equal(leftExpr, Expression.Constant(enumerator.Current, leftExpr.Type)); contains = contains == null ? next : Expression.OrElse(contains, next); } } else { // for larger sets, wire our matchingSet into this expression as constant reference // it will be kept alive by garbage collector, and will be collected when expression is collected var setContainsMethod = ReflectionHelper.GetOrAddMethod1(matchingSet.GetType(), "Contains", leftExpr.Type); contains = Expression.Call(Expression.Constant(matchingSet), setContainsMethod, leftExpr); } } if (op.StartsWith("not ")) { contains = ConstantHelper.TryEvalConst(root, contains, ExpressionType.Not, typeof(bool)); } return(contains); }
private Expression BuildBinaryExpression(ParseTreeNode root, CompilerState state) { root.RequireChildren(3); var leftNode = root.ChildNodes[0]; var leftExpr = Analyze(leftNode, state); var op = GetBinaryOperator(root.ChildNodes[1]); if (op == "in" || op == "not in") { return(BuildInclusionExpression(root, leftExpr, op, state)); } var rightNode = root.ChildNodes[2]; var rightExpr = Analyze(rightNode, state); if (!ExpressionTreeExtensions.TryAdjustVoid(ref leftExpr, ref rightExpr)) { throw new CompilationException("This operation is not defined when both arguments are void", root); } Expression expr; leftExpr = leftExpr.RemoveNullability(); rightExpr = rightExpr.RemoveNullability(); if (leftExpr.IsDateTime() && rightExpr.IsDateTime() || leftExpr.IsTimeSpan() && rightExpr.IsTimeSpan()) { #region DateTime and DateTime, or TimeSpan and TimeSpan switch (op) { case "+": if (leftExpr.IsDateTime() && rightExpr.IsDateTime()) { throw new CompilationException("Datetime values cannot be added to one another", root); } expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Add); break; case "-": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Subtract); break; case "=": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Equal); break; case "!=": case "<>": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.NotEqual); break; case ">": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.GreaterThan); break; case "<": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.LessThan); break; case "<=": case "!>": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.LessThanOrEqual); break; case ">=": case "!<": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.GreaterThanOrEqual); break; default: throw new CompilationException("Binary operator not supported for datetime values: " + op, root.ChildNodes[1]); } #endregion } else if (leftExpr.IsDateTime() && rightExpr.IsTimeSpan()) { #region DateTime and TimeSpan or TimeSpan and DateTime switch (op) { case "+": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Add); break; case "-": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Subtract); break; default: throw new CompilationException("Binary operator not supported for datetime and timespan: " + op, root.ChildNodes[1]); } #endregion } else if (leftExpr.IsTimeSpan() && rightExpr.IsDateTime()) { #region TimeSpan and DateTime switch (op) { case "+": expr = ConstantHelper.TryEvalConst(root, rightExpr, leftExpr, ExpressionType.Add); break; default: throw new CompilationException("Binary operator not supported for timespan and datetime: " + op, root.ChildNodes[1]); } #endregion } else if (leftExpr.IsString() && rightExpr.IsString()) { #region String and String switch (op) { case "+": var concat = ReflectionHelper.StringConcat; expr = ConstantHelper.TryEvalConst(root, concat, leftExpr, rightExpr); break; case "=": case "!=": case "<>": expr = PrepareStringEquality(root, leftExpr, rightExpr); if (op[0] != '=') { expr = ConstantHelper.TryEvalConst(root, expr, ExpressionType.Not, expr.Type); } break; case ">": expr = ConstantHelper.TryEvalConst(root, PrepareStringComparison(root, leftExpr, rightExpr), Expression.Constant(0), ExpressionType.GreaterThan); break; case "<": expr = ConstantHelper.TryEvalConst(root, PrepareStringComparison(root, leftExpr, rightExpr), Expression.Constant(0), ExpressionType.LessThan); break; case "<=": case "!>": expr = ConstantHelper.TryEvalConst(root, PrepareStringComparison(root, leftExpr, rightExpr), Expression.Constant(0), ExpressionType.LessThanOrEqual); break; case ">=": case "!<": expr = ConstantHelper.TryEvalConst(root, PrepareStringComparison(root, leftExpr, rightExpr), Expression.Constant(0), ExpressionType.GreaterThanOrEqual); break; case "like": throw new CompilationException("Instead of LIKE, use predefined functions StartsWith, EndsWith and Contains", root.ChildNodes[1]); default: throw new CompilationException("Binary operator not supported for strings: " + op, root.ChildNodes[1]); } #endregion } else if (leftExpr.IsNumeric() && rightExpr.IsNumeric()) { #region Numeric and Numeric ExpressionTreeExtensions.AdjustArgumentsForBinaryOperation(ref leftExpr, ref rightExpr, root); switch (op) { case "+": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Add); break; case "-": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Subtract); break; case "*": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Multiply); break; case "/": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Divide); break; case "%": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Modulo); break; case "&": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.And); break; case "|": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Or); break; case "^": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.ExclusiveOr); break; case "<": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.LessThan); break; case "<=": case "!>": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.LessThanOrEqual); break; case ">": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.GreaterThan); break; case ">=": case "!<": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.GreaterThanOrEqual); break; case "=": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Equal); break; case "!=": case "<>": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.NotEqual); break; default: throw new CompilationException("Binary operator not supported yet for numerics: " + op, root.ChildNodes[1]); } #endregion } else if (leftExpr.IsBoolean() && rightExpr.IsBoolean()) { #region Boolean and Boolean switch (op) { case "and": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.AndAlso); break; case "or": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.OrElse); break; case "xor": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.ExclusiveOr); break; case "=": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.Equal); break; case "!=": case "<>": expr = ConstantHelper.TryEvalConst(root, leftExpr, rightExpr, ExpressionType.NotEqual); break; default: throw new CompilationException("Binary operator not supported yet for booleans: " + op, root.ChildNodes[1]); } #endregion } else { throw new CompilationException(string.Format("Binary operator {0} is not yet supported for types {1} and {2}", op, leftExpr.Type.FullName, rightExpr.Type.FullName), root.ChildNodes[1]); } return(expr); }
private Expression BuildUnaryExpression(ParseTreeNode root, CompilerState state) { root.RequireChildren(2, 4); ParseTreeNode targetNode; string op; if (root.ChildNodes.Count == 2) { targetNode = root.ChildNodes[1]; op = root.ChildNodes[0].Token.ValueString; } else { // we should have IS NULL or IS NOT NULL operator here targetNode = root.ChildNodes[0]; var k1 = root.RequireChild("is", 1).Term.Name; var k2 = root.RequireChild(null, 2).Term.Name; var k3 = root.ChildNodes.Count == 4 ? root.RequireChild(null, 3).Term.Name : null; if (0 != StringComparer.OrdinalIgnoreCase.Compare(k1, "is") || (k3 == null && 0 != StringComparer.OrdinalIgnoreCase.Compare(k2, "null")) || (k3 != null && (0 != StringComparer.OrdinalIgnoreCase.Compare(k2, "not") || 0 != StringComparer.OrdinalIgnoreCase.Compare(k3, "null"))) ) { throw new CompilationException("IS NULL or IS NOT NULL expected", root); } op = k3 == null ? "is null" : "is not null"; } switch (op) { case "-": { var target = Analyze(targetNode, state).RemoveNullability(); target.RequireNumeric(targetNode); return(ConstantHelper.TryEvalConst(root, target, ExpressionType.Negate, target.Type)); } case "+": { var target = Analyze(targetNode, state).RemoveNullability(); target.RequireNumeric(targetNode); return(target); } case "~": { var target = Analyze(targetNode, state).RemoveNullability(); target.RequireInteger(targetNode); return(ConstantHelper.TryEvalConst(root, target, ExpressionType.Not, target.Type)); } case "is null": return(BuildIsNullPredicate(targetNode, state, true)); case "is not null": return(BuildIsNullPredicate(targetNode, state, false)); default: { var target = Analyze(targetNode, state).RemoveNullability(); if (0 == StringComparer.OrdinalIgnoreCase.Compare("not", op)) { target.RequireBoolean(targetNode); return(ConstantHelper.TryEvalConst(root, target, ExpressionType.Not, target.Type)); } throw new CompilationException( string.Format( "Unary operator {0} not supported for type {1}", op, target.Type.FullName), root); } } }
private static Expression PrepareStringEquality(ParseTreeNode root, Expression leftExpr, Expression rightExpr) { var equals = ReflectionHelper.StringComparerEquals; return(ConstantHelper.TryEvalConst(root, equals, Expression.Constant(StringComparer.OrdinalIgnoreCase), leftExpr, rightExpr)); }
private static Expression PrepareStringComparison(ParseTreeNode root, Expression leftExpr, Expression rightExpr) { var compare = ReflectionHelper.StringComparerCompare; return(ConstantHelper.TryEvalConst(root, compare, Expression.Constant(StringComparer.OrdinalIgnoreCase), leftExpr, rightExpr)); }