private static MethodInfo PrepareStringInstanceMethodCall(string methodName, ParseTreeNode root, CompilerState state, out Expression value, out Expression pattern)
        {
            root.RequireChildren(2);

            MethodInfo method;
            if (0 == StringComparer.OrdinalIgnoreCase.Compare(methodName, "StartsWith"))
            {
                method = ReflectionHelper.StringStartsWith;
            }
            else if (0 == StringComparer.OrdinalIgnoreCase.Compare(methodName, "EndsWith"))
            {
                method = ReflectionHelper.StringEndsWith;
            }
            else if (0 == StringComparer.OrdinalIgnoreCase.Compare(methodName, "IndexOf"))
            {
                method = ReflectionHelper.StringIndexOf;
            }
            else
            {
                throw new Exception("Could not find method " + methodName);
            }

            var arg1Node = root.RequireChild(null, 1, 0, 0);
            value = state.ParentRuntime.Analyze(arg1Node, state);
            value.RequireString(arg1Node);

            var arg2Node = root.RequireChild(null, 1, 0, 1);
            pattern = state.ParentRuntime.Analyze(arg2Node, state);
            pattern.RequireString(arg2Node);
            return method;
        }
        private Expression Analyze(ParseTree tree, CompilerState state)
        {
            if (tree == null)
            {
                throw new ArgumentNullException("tree");
            }

            if (tree.Status != ParseTreeStatus.Parsed)
            {
                throw new ArgumentException("Cannot build expression on incomplete tree");
            }

            var root = tree.Root;
            return Analyze(root, state);
        }
        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 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 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);
        }
        private Expression BuildIdentifierExpression(ParseTreeNode root, CompilerState state)
        {
            root.RequireChildren(1, 10);

            // first, analyze the first identifier in the dotted sequence
            var identifier = BuildIdentifierRootExpression(root, state);

            // next, look for any next-level dotted fields and properties
            // for these, we will not look into any other atoms or args - only into the reflected fields and properties of the object
            for (var i = 1; i < root.ChildNodes.Count; i++)
            {
                var name = root.ChildNodes[i].Token.Text; // no need to lowercase this text
                var next = TryGetFieldOrPropertyInfoFromContext(name, identifier);
                if (next == null)
                {
                    throw new CompilationException(string.Format("Could not find field or property {0} on type {1}",
                        name, identifier.Type));
                }
                identifier = next;
            }

            return identifier;
        }
Beispiel #7
0
        private static Expression TopLevelAtomExpressionGeneratorForFields(ParseTreeNode root, CompilerState compilerState)
        {
            root.RequireChildren(0);
            var pqlCompilerState = (PqlCompilerState) compilerState;
            var descriptor = pqlCompilerState.ContainerDescriptor;
            var parsedRequest = pqlCompilerState.ParsedRequest;

            var fieldName = root.Token.ValueString;
            var field = descriptor.TryGetField(parsedRequest.TargetEntity.DocumentType, fieldName);
            if (field == null)
            {
                // convention is for handlers to return nulls for unknown atoms
                return null;
            }

            return GetOrAddFieldRefToCompilationContext(parsedRequest, pqlCompilerState, field);
        }
        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 BuildNumericConstantExpression(ParseTreeNode root, CompilerState state)
        {
            root.RequireChildren(0);

            var objValue = root.Token.Value;
            if (objValue != null && objValue.GetType().IsNumeric())
            {
                return Expression.Constant(objValue);
            }

            throw new CompilationException("Invalid numeric constant: " + root.Token.Text, root);
        }
        private Expression BuildCaseStatementExpression(ParseTreeNode root, CompilerState state)
        {
            root.RequireChildren(4, 5);
            root.RequireChild("CASE", 0);
            var caseVariableNode = root.RequireChild("caseVariable", 1);
            caseVariableNode.RequireChildren(0, 1);

            ParseTreeNode caseDefaultValueNode = null;
            if (root.ChildNodes.Count > 4)
            {
                var caseDefaultNode = root.RequireChild("caseDefault", 3);
                if (caseDefaultNode.ChildNodes.Count > 0)
                {
                    caseDefaultNode.RequireChildren(2);
                    caseDefaultNode.RequireChild("else", 0);
                    caseDefaultValueNode = caseDefaultNode.RequireChild(null, 1);
                }
            }

            root.RequireChild("END", root.ChildNodes.Count - 1);

            // first get the expression for ELSE
            var caseDefault = caseDefaultValueNode != null ? Analyze(caseDefaultValueNode, state) : null;

            var whenThenListNode = root.RequireChild("caseWhenList", 2);
            whenThenListNode.RequireChildren(1, 100);

            // now if we don't have an expression to be analyzed, just create a sequence of IIFs
            if (caseVariableNode.ChildNodes.Count == 0)
            {
                return BuildCaseStatementExpression(state, whenThenListNode, caseDefault);
            }

            // but when there is a variable, type checking has different rules and output is SWITCH statement
            return BuildSwitchStatementExpression(state, caseVariableNode, whenThenListNode, caseDefault);
        }
        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 object CompileImpl(Expression expression, CompilerState state)
        {
            if (state == null)
            {
                throw new ArgumentNullException("state");
            }

            state.RawReturnType = expression.Type;

            while (expression.CanReduce)
            {
                expression = expression.Reduce();
            }

            LambdaExpression lambda;
            var paramTypes = state.CompileToAction ? new Type[state.Arguments.Count] : new Type[state.Arguments.Count + 1];
            for (var i = 0; i < state.Arguments.Count; i++)
            {
                paramTypes[i] = state.Arguments[i].Type;
            }

            if (state.CompileToAction)
            {
                lambda = Expression.Lambda(Expression.GetActionType(paramTypes), expression, state.Arguments);
            }
            else
            {
                var returnType = state.ReturnType ?? state.RawReturnType;
                paramTypes[paramTypes.Length - 1] = returnType;

                expression = ExpressionTreeExtensions.AdjustReturnType(null, expression, returnType);
                lambda = Expression.Lambda(expression, state.Arguments);

                if (!ReferenceEquals(returnType, lambda.ReturnType))
                {
                    throw new CompilationException(string.Format("Expected return type: {0}. Real return type: {1}", returnType.FullName, lambda.ReturnType.FullName));
                }
            }

            if (lambda.Parameters.Count != state.Arguments.Count)
            {
                throw new CompilationException(string.Format("Expected number of arguments: {0}. Real number: {1}", state.Arguments.Count, lambda.Parameters.Count));
            }

            for (var i = 0; i < lambda.Parameters.Count; i++)
            {
                if (!ReferenceEquals(lambda.Parameters[i].Type, state.Arguments[i].Type))
                {
                    throw new CompilationException(
                        string.Format("Expected type of argument {0}: {1}. Real type: {2}", i, state.Arguments[i].Type.FullName, lambda.Parameters[i].Type.FullName));
                }
            }

            return lambda.Compile();
        }
        /// <summary>
        /// DO NOT REMOVE! Used implicitly, via generic method instantiation.
        /// </summary>
        public static HashSet <T> EnumerateValues <T>(IExpressionEvaluatorRuntime runtime, ParseTreeNode root, CompilerState state)
        {
            if (runtime == null)
            {
                throw new ArgumentNullException("runtime");
            }

            if (root == null)
            {
                throw new ArgumentNullException("root");
            }

            if (state == null)
            {
                throw new ArgumentNullException("state");
            }

            var isString = ReferenceEquals(typeof(T), typeof(string));
            var result   = isString ? new HashSet <T>((IEqualityComparer <T>)StringComparer.OrdinalIgnoreCase) : new HashSet <T>();

            foreach (var childNode in root.ChildNodes)
            {
                var item = runtime.Analyze(childNode, state);
                item.RequireNonVoid(childNode);

                try
                {
                    result.Add((T)Convert.ChangeType(((ConstantExpression)item).Value, typeof(T)));
                }
                catch
                {
                    throw new CompilationException(
                              String.Format(
                                  "All items in the IN arguments list must be constants of type compatible with {0}. Actual expression found: {1}, of type {2}",
                                  typeof(T).FullName, item.NodeType, item.Type), childNode);
                }
            }

            return(result);
        }
Beispiel #14
0
        private static Expression TopLevelAtomExpressionGeneratorForParameters(ParseTreeNode root, CompilerState compilerState)
        {
            root.RequireChildren(0);
            var pqlCompilerState = (PqlCompilerState) compilerState;
            var parsedRequest = pqlCompilerState.ParsedRequest;

            var names = pqlCompilerState.RequestParameters.Names;
            var paramName = root.Token.ValueString;
            if (string.IsNullOrEmpty(paramName) || paramName[0] != '@')
            {
                // convention is for handlers to return nulls for unknown atoms
                return null;
            }

            if (names != null)
            {
                for (var ordinal = 0; ordinal < names.Length; ordinal++)
                {
                    if (0 == StringComparer.OrdinalIgnoreCase.Compare(names[ordinal], paramName))
                    {
                        return GetOrAddParameterRefToCompilationContext(parsedRequest, pqlCompilerState, ordinal);
                    }
                }
            }

            throw new CompilationException("Undefined parameter: " + paramName, root);
        }
        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 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 BuildFunCallExpression(ParseTreeNode root, CompilerState state)
        {
            // funCall -> Id
            var idNode = root.RequireChild("Id", 0);
            idNode.RequireChildren(1); // dotted identifiers are not supported for function calls

            // funCall -> funArgs
            root.RequireChild("funArgs", 1);

            AtomMetadata atom;
            var name = idNode.ChildNodes[0].Token.ValueString;
            if (!m_atoms.TryGetValue(name, out atom) || atom.AtomType != AtomType.Function)
            {
                throw new CompilationException("Unknown function: " + name, root);
            }

            if (atom.MethodInfo != null)
            {
                return BuildFunCallExpressionFromMethodInfo(atom, root, state);
            }

            if (atom.ExpressionGenerator != null)
            {
                return atom.ExpressionGenerator(root, state);
            }

            // internal error, somebody screwed up with configuration of runtime
            throw new Exception("ExpressionGenerator and MethodInfo are both null on atom: " + atom.Name);
        }
 private Expression BuildStringLiteralExpression(ParseTreeNode root, CompilerState state)
 {
     root.RequireChildren(0);
     var value = root.Token.ValueString;
     var expr = string.IsNullOrEmpty(value)
         ? Expression.Constant(null, typeof (String)) : Expression.Constant(PreprocessStringLiteral(value, root));
     return expr;
 }
        private Expression BuildFunCallExpressionFromMethodInfo(AtomMetadata atom, ParseTreeNode root, CompilerState state)
        {
            // number of arguments must exactly match number of child nodes in the tree
            var paramInfo = atom.MethodInfo.GetParameters();

            var funArgs = root.RequireChild("exprList", 1, 0);
            funArgs.RequireChildren(paramInfo.Length);

            // types and order of arguments must match nodes in the tree
            var args = new Expression[paramInfo.Length];

            for (var i = 0; i < paramInfo.Length; i++)
            {
                var param = paramInfo[i];
                var argNode = funArgs.ChildNodes[i];
                var value = state.ParentRuntime.Analyze(argNode, state);

                Expression adjusted;
                if (!ExpressionTreeExtensions.TryAdjustReturnType(root, value, param.ParameterType, out adjusted))
                {
                    throw new CompilationException(string.Format("Could not adjust parameter number {0} to invoke function {1}",
                        i, atom.Name), funArgs.ChildNodes[i]);
                }

                args[i] = adjusted;
            }

            return BuildFunctorInvokeExpression(atom, args);
        }
        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);
                    }
            }
        }
Beispiel #21
0
        private static Expression PredefinedAtom_RowNumberInPage(ParseTreeNode root, CompilerState compilerState)
        {
            var funArgs = ExpressionTreeExtensions.UnwindTupleExprList(root.RequireChild("funArgs", 1));
            funArgs.RequireChildren(0);

            return Expression.Field(compilerState.Context, "RowNumberInOutput");
        }