private Expression PredefinedAtom_SetContains(ParseTreeNode root, CompilerState state) { root.RequireChildren(2); var arg1Node = root.RequireChild(null, 1, 0, 0); var hashset = state.ParentRuntime.Analyze(arg1Node, state); hashset.RequireNonVoid(arg1Node); if (hashset.Type.IsValueType) { throw new CompilationException("Set must be of reference type", arg1Node); } if (hashset is ConstantExpression) { throw new CompilationException("Set must not be a constant expression", arg1Node); } var arg2Node = root.RequireChild(null, 1, 0, 1); var element = state.ParentRuntime.Analyze(arg2Node, state); var methods = hashset.Type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.FlattenHierarchy); foreach (var method in methods) { if (method.Name.Equals("Contains")) { var methodArgs = method.GetParameters(); if (methodArgs.Length == 1 && methodArgs[0].ParameterType != typeof(object)) { Expression adjusted; if (ExpressionTreeExtensions.TryAdjustReturnType(arg2Node, element, methodArgs[0].ParameterType, out adjusted)) { return(Expression.Condition( Expression.ReferenceEqual(hashset, Expression.Constant(null)), Expression.Constant(false, typeof(bool)), Expression.Call(hashset, method, adjusted))); } } } } throw new CompilationException( "Could not find a public instance method 'Contains' to match element type " + element.Type.FullName, arg1Node); }
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)); }
/// <summary> /// Produces .NET Expression object from the given abstract syntax tree. /// Supports re-entrancy, useful for expression generators (see <see cref="AtomMetadata.ExpressionGenerator"/>). /// </summary> /// <param name="root">Parse tree root</param> /// <param name="state">Compiler state</param> /// <returns>Expression node with verified logical data type</returns> /// <exception cref="ArgumentNullException"><paramref name="root"/> is null</exception> /// <exception cref="ArgumentNullException"><paramref name="state"/> is null</exception> /// <exception cref="CompilationException">Compilation errors, check exception details</exception> public Expression Analyze(ParseTreeNode root, CompilerState state) { switch (root.Term.Name) { case "exprList": case "tuple": root.RequireChildren(1); var newRoot = ExpressionTreeExtensions.UnwindTupleExprList(root.ChildNodes[0]); return(Analyze(newRoot, state)); case "betweenExpr": return(BuildBetweenExpression(root, state)); case "binExpr": return(BuildBinaryExpression(root, state)); case "unExpr": return(BuildUnaryExpression(root, state)); case "case": return(BuildCaseStatementExpression(root, state)); case "Id": return(BuildIdentifierExpression(root, state)); case "number": return(BuildNumericConstantExpression(root, state)); case "string": return(BuildStringLiteralExpression(root, state)); case "funCall": return(BuildFunCallExpression(root, state)); default: throw new CompilationException("Term not yet supported: " + root.Term.Name, root); } }
private static Expression PredefinedAtom_Null(ParseTreeNode root, CompilerState state) { // clients are responsible for translating this expression into appropriate type return(ExpressionTreeExtensions.MakeNewNullable(typeof(UnboxableNullable <ExpressionTreeExtensions.VoidTypeMarker>))); }
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 BuildCaseStatementExpression(CompilerState state, ParseTreeNode whenThenListNode, Expression caseDefault) { // now start building on top of the tail, right to left, // also making sure that types are compatible var tail = caseDefault; for (var i = whenThenListNode.ChildNodes.Count - 1; i >= 0; i--) { var caseWhenThenNode = whenThenListNode.RequireChild("caseWhenThen", i); caseWhenThenNode.RequireChildren(4); var whenNode = caseWhenThenNode.RequireChild(null, 1); var thenNode = caseWhenThenNode.RequireChild(null, 3); if (whenNode.Term.Name == "tuple") { throw new CompilationException("When variable for CASE is not specified, you can only have one expression in each WHEN clause", whenNode); } // in this flavor of CASE, we are building a sequence of IIFs // it requires that our "WHEN" clause is of non-nullable boolean type var when = Analyze(whenNode, state).RemoveNullability(); when.RequireBoolean(whenNode); var then = Analyze(thenNode, state); // try to auto-adjust types of this "THEN" and current tail expression if needed if (tail != null) { Expression adjusted; if (ExpressionTreeExtensions.TryAdjustReturnType(thenNode, then, tail.Type, out adjusted)) { then = adjusted; } else if (ExpressionTreeExtensions.TryAdjustReturnType(thenNode, tail, then.Type, out adjusted)) { tail = adjusted; } else { throw new CompilationException( string.Format( "Incompatible types within CASE statement. Tail is of type {0}, and then is of type {1}", tail.Type.FullName, then.Type.FullName), thenNode); } } if (when is ConstantExpression) { if ((bool)((ConstantExpression)when).Value) { tail = then; } } else { tail = Expression.Condition(when, then, tail ?? ExpressionTreeExtensions.GetDefaultExpression(then.Type)); } } return(tail); }
private Expression BuildSwitchStatementExpression(CompilerState state, ParseTreeNode caseVariableNode, ParseTreeNode whenThenListNode, Expression caseDefault) { var switchVariable = Analyze(caseVariableNode.ChildNodes[0], state); switchVariable.RequireNonVoid(caseVariableNode.ChildNodes[0]); if (switchVariable is ConstantExpression) { throw new CompilationException("CASE variable should not be a constant value", caseVariableNode); } var cases = new List <Tuple <Expression[], Expression, ParseTreeNode> >(whenThenListNode.ChildNodes.Count); Expression firstNonVoidThen = null; var mustReturnNullable = false; foreach (var caseWhenThenNode in whenThenListNode.ChildNodes) { caseWhenThenNode.RequireChildren(4); var whenNodesRoot = ExpressionTreeExtensions.UnwindTupleExprList(caseWhenThenNode.RequireChild(null, 1)); var thenNode = caseWhenThenNode.RequireChild(null, 3); IList <ParseTreeNode> whenNodes; if (whenNodesRoot.Term.Name == "exprList") { whenNodes = whenNodesRoot.ChildNodes; } else { whenNodes = new[] { whenNodesRoot }; } var when = new Expression[whenNodes.Count]; for (var i = 0; i < whenNodes.Count; i++) { var whenNode = whenNodes[i]; when[i] = Analyze(whenNode, state); if (!when[i].IsVoid() && !(when[i] is ConstantExpression)) { throw new CompilationException("CASE statement with a test variable requires WHEN clauses to be constant values", whenNode); } Expression adjusted; if (ExpressionTreeExtensions.TryAdjustReturnType(whenNode, when[i], switchVariable.Type, out adjusted)) { when[i] = adjusted; } else { throw new CompilationException( string.Format( "Could not adjust WHEN value type {0} to CASE argument type {1}", when[i].Type.FullName, switchVariable.Type.FullName), whenNode); } } var then = Analyze(thenNode, state); cases.Add(new Tuple <Expression[], Expression, ParseTreeNode>(when, then, thenNode)); if (then.IsVoid()) { // if there is at least one "void" return value, resulting value must be nullable mustReturnNullable = true; } else if (firstNonVoidThen == null) { firstNonVoidThen = then; } } if (firstNonVoidThen == null && !caseDefault.IsVoid()) { firstNonVoidThen = caseDefault; } var adjustedCaseDefault = caseDefault; // now try to adjust whatever remaining VOID "then-s" to the first-met non-void then // if all THENs are void, then just leave it as-is - type will be adjusted by caller if (firstNonVoidThen != null) { if (mustReturnNullable && firstNonVoidThen.Type.IsValueType && !firstNonVoidThen.IsNullableType()) { firstNonVoidThen = ExpressionTreeExtensions.MakeNewNullable( typeof(UnboxableNullable <>).MakeGenericType(firstNonVoidThen.Type), firstNonVoidThen); } for (var i = 0; i < cases.Count; i++) { var thenNode = cases[i].Item3; var then = cases[i].Item2; if (!ReferenceEquals(then, firstNonVoidThen) && then.IsVoid()) { Expression adjusted; if (ExpressionTreeExtensions.TryAdjustReturnType(thenNode, then, firstNonVoidThen.Type, out adjusted)) { cases[i] = new Tuple <Expression[], Expression, ParseTreeNode>(cases[i].Item1, adjusted, cases[i].Item3); } else { throw new CompilationException( string.Format( "Could not adjust THEN value type {0} to first-met THEN value type {1}", then.Type.FullName, firstNonVoidThen.Type.FullName), thenNode); } } } if (caseDefault != null && !ExpressionTreeExtensions.TryAdjustReturnType(caseVariableNode, caseDefault, firstNonVoidThen.Type, out adjustedCaseDefault)) { throw new CompilationException( string.Format( "Could not adjust CASE default value's type {0} to first-met THEN value type {1}", caseDefault.Type.FullName, firstNonVoidThen.Type.FullName), caseVariableNode); } } if (adjustedCaseDefault == null) { adjustedCaseDefault = ExpressionTreeExtensions.GetDefaultExpression( firstNonVoidThen == null ? typeof(UnboxableNullable <ExpressionTreeExtensions.VoidTypeMarker>) : firstNonVoidThen.Type); } return(Expression.Switch( switchVariable, adjustedCaseDefault, null, cases.Select(x => Expression.SwitchCase(x.Item2, x.Item1)))); }
private Expression BuildIdentifierRootExpression(ParseTreeNode root, CompilerState state) { AtomMetadata atom; var name = root.ChildNodes[0].Token.ValueString; // first, look for an argument with this name var argument = state.TryGetArgumentByName(name); if (argument != null) { return(argument); } var context = state.Context; // next, see if we have a field or property on the context (if any context present) var contextBoundExpression = TryGetFieldOrPropertyInfoFromContext(name, context); if (contextBoundExpression != null) { return(contextBoundExpression); } // and only then look through available IDENTIFIER atoms if (m_atoms.TryGetValue(name, out atom) && atom.AtomType == AtomType.Identifier) { if (atom.ExpressionGenerator != null) { return(atom.ExpressionGenerator(root, state)); } if (atom.MethodInfo == null) { // internal error, somebody screwed up with configuration of runtime throw new Exception("ExpressionGenerator and MethodInfo are both null on atom: " + atom.Name); } // no arguments? great var paramInfo = atom.MethodInfo.GetParameters(); if (paramInfo.Length == 0) { return(BuildFunctorInvokeExpression(atom, (Expression[])null)); } // any arguments? must have exactly one argument, context must be registered, and context type must be adjustable to this method's arg type if (context == null) { throw new CompilationException("Atom's MethodInfo cannot be used for an Id expression, because context is not available: " + atom.Name, root); } Expression adjustedContext; if (paramInfo.Length > 1 || !ExpressionTreeExtensions.TryAdjustReturnType(root, context, paramInfo[0].ParameterType, out adjustedContext)) { throw new CompilationException("Atom's MethodInfo may only have either zero arguments or one argument of the same type as expression context: " + atom.Name, root); } return(BuildFunctorInvokeExpression(atom, adjustedContext)); } // still nothing found? ask IDENTIFIER atom handlers foreach (var handler in m_atomHandlers) { if (handler.AtomType != AtomType.Identifier) { continue; } if (handler.ExpressionGenerator == null) { // internal error, somebody screwed up with configuration of runtime throw new Exception("ExpressionGenerator is null on atom handler: " + handler.Name); } // only pass the first portion of dot-notation identifier to handler var result = handler.ExpressionGenerator(root.ChildNodes[0], state); if (result != null) { return(result); } } throw new CompilationException("Unknown atom: " + name, root); }