#pragma warning restore 649 /// <summary> /// Process a result operator. If this result is amenable to be made into a function, then /// do so. /// </summary> /// <param name="resultOperator"></param> /// <param name="queryModel"></param> /// <param name="index"></param> public override void VisitResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, int index) { // Look for a single-result processor var processor = _operators.FindScalarROProcessor(resultOperator.GetType()); if (processor != null) { var result = processor.ProcessResultOperator(resultOperator, queryModel, _codeEnv, _codeContext, MEFContainer); if (result != null) { _codeEnv.SetResult(result); _scoping.Add(_codeContext.Add(queryModel, result)); } return; } // Look for a sequence processor var collectionProcessor = _operators.FindCollectionROProcessor(resultOperator.GetType()); if (collectionProcessor != null) { collectionProcessor.ProcessResultOperator(resultOperator, queryModel, _codeEnv, _codeContext, MEFContainer); _codeEnv.ResetResult(); return; } /// /// Uh oh - no idea how to do this! /// throw new InvalidOperationException("LINQToTTree can't translate the operator '" + resultOperator.ToString() + "'"); }
/// <summary> /// If one of the helper functions needs to be parsed, we end up here. /// Note: this is a bit tricky, and, worse, this is not tested (too complex :-)). /// </summary> /// <param name="expr"></param> /// <param name="result"></param> /// <param name="gc"></param> /// <param name="context"></param> /// <returns></returns> public System.Linq.Expressions.Expression ProcessMethodCall(MethodCallExpression expr, IGeneratedQueryCode gc, ICodeContext context, CompositionContainer container) { if (expr == null) { throw new ArgumentNullException("expr"); } if (expr.Method.Name == "ApplyReturnFirst") { /// /// Load out the parameter names we are looking at so we cna do the translation. /// var parameters = expr.Method.GetParameters(); var action = RaiseLambda(expr.Arguments[2]); var methodGenericArguments = expr.Method.GetGenericArguments(); var actionType = typeof(Action <,>).MakeGenericType(new Type[] { methodGenericArguments[0], methodGenericArguments[1] }); var expressionGeneric = typeof(Expression <>).MakeGenericType(new Type[] { actionType }); var parameterSpec = expressionGeneric.GetProperty("Parameters"); var lambdaParameters = (parameterSpec.GetValue(action, null) as IEnumerable <ParameterExpression>).ToArray(); /// /// Next, do the lambda expression. Order of p1 and p2 is b/c we should make sure that it happens /// before any parameters are replaced! Note we parse the body of the lambda here! Parameters are defined and should /// correctly deal with any substitution in process. /// var p2 = context.Add(lambdaParameters[1].Name, expr.Arguments[1]); var p1 = context.Add(lambdaParameters[0].Name, expr.Arguments[0]); var statementBody = ExpressionToCPP.GetExpression(action.Body.Resolve(gc, context, container), gc, context, container); p1.Pop(); p2.Pop(); gc.Add(new Statements.StatementSimpleStatement(statementBody.RawValue, resultVars: new string[0] { }, dependentVars: statementBody.Dependants.Select(i => i.RawValue).ToArray())); /// /// Finally, what we will return if this is the last thing we are doing! /// return(expr.Arguments[0]); } else { throw new NotImplementedException("Helpers." + expr.Method.Name + " is not handled!"); } }
/// <summary> /// Parse the group by enum into an arrary. For us, this is a loop over the vector array of the second argument of /// the iterator that was created in the above array info factory. :-) /// </summary> /// <param name="expr"></param> /// <param name="gc"></param> /// <param name="cc"></param> /// <param name="container"></param> /// <param name="ReGetIArrayInfo"></param> /// <returns></returns> public IArrayInfo GetIArrayInfo(Expression expr, IGeneratedQueryCode gc, ICodeContext cc, CompositionContainer container, Func <Expression, IArrayInfo> ReGetIArrayInfo) { // // Make sure this is something we want // BaseGroupInfo groupObj; if (expr.Type.IsGenericType && expr.Type.GetGenericTypeDefinition() == typeof(IGrouping <int, int>).GetGenericTypeDefinition() && cc.LoopVariable.Type.IsGenericType && cc.LoopVariable.Type.GetGenericTypeDefinition() == typeof(GroupByType <int, int>).GetGenericTypeDefinition()) { var param = cc.LoopVariable as ConstantExpression; if (param == null) { return(null); } groupObj = param.Value as BaseGroupInfo; } else if (expr is ConstantExpression && expr.Type.IsGenericType && expr.Type.GetGenericTypeDefinition() == typeof(GroupByType <int, int>).GetGenericTypeDefinition()) { groupObj = (expr as ConstantExpression).Value as BaseGroupInfo; } else { return(null); } if (groupObj == null) { throw new InvalidOperationException("Group by type object has a null value - should never happen!"); } // // Ok, now code up the loop over the interior. // var s = new Statements.StatementLoopOverGroupItems(groupObj.GroupLoopStatement.GroupItemsReference.PerformAllSubstitutions(cc)); gc.Add(s); // // Finally, return the loop index variable. // We queue up a replacement as well - so we can make sure that when the original loop variable is referenced, we // are actually referencing the interior one. // var loopExpression = groupObj.TargetExpression; cc.Add(groupObj.TargetExpressionLoopVariable.RawValue, s.LoopItemIndex.AsExpression()); return(new SimpleLoopVarSetting(loopExpression, s.Counter)); }
/// <summary> /// Deal with the aggregate operator coming in here. /// </summary> /// <param name="resultOperator"></param> /// <param name="queryModel"></param> /// <param name="_codeEnv"></param> /// <returns></returns> public Expression ProcessResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, IGeneratedQueryCode _codeEnv, ICodeContext context, CompositionContainer container) { /// /// Basic code checks /// AggregateFromSeedResultOperator a = resultOperator as AggregateFromSeedResultOperator; if (a == null) { throw new ArgumentNullException("result Operator must not be null and must be of type AggregateFromSeedResultOperator!"); } if (a.Func.Parameters.Count != 1) { throw new InvalidOperationException("Aggregate only allows for a function with one parameters!"); } if (a.OptionalResultSelector != null) { throw new NotImplementedException("Can't do a selector function yet"); } // We need to declare a variable to hold the seed and its updates - the accumulator // We then need to write the code that does the update to the seed. // Finally, if there is a final function, we need to call that after the loop is done! var accumulator = DeclarableParameter.CreateDeclarableParameterExpression(a.Seed.Type); var newGC = new GeneratedCode(blockShouldBeBraced: false); var newCC = new CodeContext(); accumulator.InitialValue = ExpressionToCPP.GetExpression(a.Seed, newGC, newCC, container); if (newGC.CodeBody.Statements.Count() > 0) { accumulator.InitialValueCode = newGC; } _codeEnv.QueueForTransferFromGC(newGC); /// /// Now, parse the lambda expression, doing a substitution with this guy! Note that the only argument is our /// accumulator - the other arguments have all been replaced with subqueryexpressions and the like! /// var p1 = context.Add(a.Func.Parameters[0].Name, accumulator); var funcResolved = ExpressionToCPP.GetExpression(a.Func.Body, _codeEnv, context, container); p1.Pop(); if (accumulator.RawValue != funcResolved.RawValue) { _codeEnv.Add(new Statements.StatementAggregate(accumulator, funcResolved)); } return(accumulator); }
/// <summary> /// Given array info, code a loop over it. /// </summary> /// <param name="query">The query this loop is associated with</param> /// <param name="arrayRef">The reference to the array</param> /// <remarks>Will add the query to the code context to forward to the variable that is being dealt with here.</remarks> public static IVariableScopeHolder CodeLoopOverArrayInfo(this IArrayInfo arrayRef, IQuerySource query, IGeneratedQueryCode gc, ICodeContext cc, CompositionContainer container) { var indexVar = arrayRef.AddLoop(gc, cc, container); if (indexVar == null) return null; /// /// Next, make sure the index variable can be used for later references! /// var result = cc.Add(query, indexVar.Item1); cc.SetLoopVariable(indexVar.Item1, indexVar.Item2); return result; }
/// <summary> /// Given array info, code a loop over it. /// </summary> /// <param name="query">The query this loop is associated with</param> /// <param name="arrayRef">The reference to the array</param> /// <remarks>Will add the query to the code context to forward to the variable that is being dealt with here.</remarks> public static IVariableScopeHolder CodeLoopOverArrayInfo(this IArrayInfo arrayRef, IQuerySource query, IGeneratedQueryCode gc, ICodeContext cc, CompositionContainer container) { var indexVar = arrayRef.AddLoop(gc, cc, container); if (indexVar == null) { return(null); } /// /// Next, make sure the index variable can be used for later references! /// var result = cc.Add(query, indexVar.Item1); cc.SetLoopVariable(indexVar.Item1, indexVar.Item2); return(result); }
/// <summary> /// Process the First/last. This means adding a pointer (or not if we are looking at a plane type) and /// then filling it till it is full or filling it till the loop is done. Bomb out if we are asked to at the end!! /// </summary> /// <param name="resultOperator"></param> /// <param name="queryModel"></param> /// <param name="_codeEnv"></param> /// <returns></returns> public Expression ProcessResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, IGeneratedQueryCode gc, ICodeContext cc, CompositionContainer container) { /// /// First, do data normalization /// var asFirst = resultOperator as FirstResultOperator; var asLast = resultOperator as LastResultOperator; if (asFirst == null && asLast == null) { throw new ArgumentNullException("First/Last operator must be either first or last, and not null!"); } bool isFirst = asFirst != null; bool bombIfNothing = true; if (isFirst) { bombIfNothing = !asFirst.ReturnDefaultWhenEmpty; } else { bombIfNothing = !asLast.ReturnDefaultWhenEmpty; } // // Figure out if we need to cache the result: // - simple variable which has a default value which can be used later on. // like a double, etc. // - We actually allow for a default variable. // bool cacheResult = cc.LoopVariable.Type.IsNumberType(); cacheResult = cacheResult && !bombIfNothing; // // Next, make sure we are looping over something. This had better be an array we are looking at! // if (cc.LoopIndexVariable == null) { throw new InvalidOperationException(string.Format("Can't apply First operator when we aren't looping over some well formed array '{0}'", cc.LoopVariable.ToString())); } var indexExpr = cc.LoopIndexVariable; // // We need to hold onto either the first or the last item here, so we create a statement that holds nnto the // first or the last time. It also has to mark the thing as valid! It will break when it is done. // While the bool can be used later on to get at the exception we might be throwing, the actual // result may be used much further on down. To protect against that, we set the array index to be -1, // and then hope there is a crash later on! :-) // // It is possible that this is part of a dual selection. For example, if you are interested in the jet that has the closest track, and the // loop is constructed over the jets first, and then the tracks. This First will likely be for a track index, but we will be looking up the // track later. So we need to record both the jet and track index. To get the other indicies, we just look for all loop variables between here and // the result scope. // var valueWasSeen = DeclarableParameter.CreateDeclarableParameterExpression(typeof(bool)); var indexSeen = DeclarableParameter.CreateDeclarableParameterExpression(indexExpr.Type); if (indexSeen.Type.IsNumberType()) { indexSeen.SetInitialValue("-1"); } gc.AddAtResultScope(valueWasSeen); gc.AddAtResultScope(indexSeen); var rv = new Statements.StatementRecordValue(indexSeen, indexExpr, valueWasSeen, isFirst); gc.Add(rv); foreach (var v in gc.GetUsedQuerySourceVariables(rv, indexExpr)) { var saver = DeclarableParameter.CreateDeclarableParameterExpression(v.Type); gc.AddAtResultScope(saver); rv.AddNewSaver(saver, v); cc.Add(v.RawValue, saver); } gc.Pop(true); if (bombIfNothing) { var test = ExpressionToCPP.GetExpression(Expression.Not(valueWasSeen), gc, cc, container); gc.Add(new Statements.StatementThrowIfTrue(test, string.Format("First/Last predicate executed on a null sequence: {0}", queryModel.ToString()))); } // // Finally, we need the new expression. For this we basically just ask for the translated expression. We // also add a substitution for later on for more complex expressions. // var firstlastValue = cc.LoopVariable; cc.Add(indexExpr.RawValue, indexSeen); Debug.WriteLine("First/Last: {0} for QM {1}", indexSeen.ToString(), queryModel.ToString()); // Reset the expression we are looking at in the loop. var newIndexExpr = firstlastValue.ReplaceSubExpression(indexExpr.AsExpression(), indexSeen); cc.SetLoopVariable(newIndexExpr, indexSeen); if (cacheResult) { // // Set the default value // var actualValue = DeclarableParameter.CreateDeclarableParameterExpression(cc.LoopVariable.Type); actualValue.SetInitialValue("0"); // // If everything went well, then we can do the assignment. Otherwise, we leave // it as above (having the default value). // gc.Add(new Statements.StatementFilter(valueWasSeen)); gc.Add(new Statements.StatementAssign(actualValue, ExpressionToCPP.GetExpression(firstlastValue, gc, cc, container))); gc.Pop(); return(actualValue); } else { // No need to cache the result - so no need to add extra code. return(newIndexExpr); } }
/// <summary> /// If one of the helper functions needs to be parsed, we end up here. /// Note: this is a bit tricky, and, worse, this is not tested (too complex :-)). /// </summary> /// <param name="expr"></param> /// <param name="result"></param> /// <param name="gc"></param> /// <param name="context"></param> /// <returns></returns> public System.Linq.Expressions.Expression ProcessMethodCall(MethodCallExpression expr, IGeneratedQueryCode gc, ICodeContext context, CompositionContainer container) { if (expr == null) throw new ArgumentNullException("expr"); if (expr.Method.Name == "ApplyReturnFirst") { /// /// Load out the parameter names we are looking at so we cna do the translation. /// var parameters = expr.Method.GetParameters(); var action = RaiseLambda(expr.Arguments[2]); var methodGenericArguments = expr.Method.GetGenericArguments(); var actionType = typeof(Action<,>).MakeGenericType(new Type[] { methodGenericArguments[0], methodGenericArguments[1] }); var expressionGeneric = typeof(Expression<>).MakeGenericType(new Type[] { actionType }); var parameterSpec = expressionGeneric.GetProperty("Parameters"); var lambdaParameters = (parameterSpec.GetValue(action, null) as IEnumerable<ParameterExpression>).ToArray(); /// /// Next, do the lambda expression. Order of p1 and p2 is b/c we should make sure that it happens /// before any parameters are replaced! Note we parse the body of the lambda here! Parameters are defined and should /// correctly deal with any substitution in process. /// var p2 = context.Add(lambdaParameters[1].Name, expr.Arguments[1]); var p1 = context.Add(lambdaParameters[0].Name, expr.Arguments[0]); var statementBody = ExpressionToCPP.GetExpression(action.Body.Resolve(gc, context, container), gc, context, container); p1.Pop(); p2.Pop(); gc.Add(new Statements.StatementSimpleStatement(statementBody.RawValue, resultVars: new string[0] { }, dependentVars: statementBody.Dependants.Select(i => i.RawValue).ToArray())); /// /// Finally, what we will return if this is the last thing we are doing! /// return expr.Arguments[0]; } else { throw new NotImplementedException("Helpers." + expr.Method.Name + " is not handled!"); } }
/// <summary> /// Process the First/last. This means adding a pointer (or not if we are looking at a plane type) and /// then filling it till it is full or filling it till the loop is done. Bomb out if we are asked to at the end!! /// </summary> /// <param name="resultOperator"></param> /// <param name="queryModel"></param> /// <param name="_codeEnv"></param> /// <returns></returns> public Expression ProcessResultOperator(ResultOperatorBase resultOperator, QueryModel queryModel, IGeneratedQueryCode gc, ICodeContext cc, CompositionContainer container) { /// /// First, do data normalization /// var asFirst = resultOperator as FirstResultOperator; var asLast = resultOperator as LastResultOperator; if (asFirst == null && asLast == null) { throw new ArgumentNullException("First/Last operator must be either first or last, and not null!"); } bool isFirst = asFirst != null; bool bombIfNothing = true; if (isFirst) { bombIfNothing = !asFirst.ReturnDefaultWhenEmpty; } else { bombIfNothing = !asLast.ReturnDefaultWhenEmpty; } // // Figure out if we need to cache the result: // - simple variable which has a default value which can be used later on. // like a double, etc. // - We actually allow for a default variable. // bool cacheResult = cc.LoopVariable.Type.IsNumberType(); cacheResult = cacheResult && !bombIfNothing; // // Next, make sure we are looping over something. This had better be an array we are looking at! // if (cc.LoopIndexVariable == null) { throw new InvalidOperationException(string.Format("Can't apply First operator when we aren't looping over some well formed array '{0}'", cc.LoopVariable.ToString())); } var indexExpr = cc.LoopIndexVariable; // // We need to hold onto either the first or the last item here, so we create a statement that holds nnto the // first or the last time. It also has to mark the thing as valid! It will break when it is done. // While the bool can be used later on to get at the exception we might be throwing, the actual // result may be used much further on down. To protect against that, we set the array index to be -1, // and then hope there is a crash later on! :-) // // It is possible that this is part of a dual selection. For example, if you are interested in the jet that has the closest track, and the // loop is constructed over the jets first, and then the tracks. This First will likely be for a track index, but we will be looking up the // track later. So we need to record both the jet and track index. To get the other indicies, we just look for all loop variables between here and // the result scope. // var valueWasSeen = DeclarableParameter.CreateDeclarableParameterExpression(typeof(bool)); var indexSeen = DeclarableParameter.CreateDeclarableParameterExpression(indexExpr.Type); if (indexSeen.Type.IsNumberType()) indexSeen.SetInitialValue("-1"); gc.AddAtResultScope(valueWasSeen); gc.AddAtResultScope(indexSeen); var rv = new Statements.StatementRecordValue(indexSeen, indexExpr, valueWasSeen, isFirst); gc.Add(rv); foreach (var v in gc.GetUsedQuerySourceVariables(rv, indexExpr)) { var saver = DeclarableParameter.CreateDeclarableParameterExpression(v.Type); gc.AddAtResultScope(saver); rv.AddNewSaver(saver, v); cc.Add(v.RawValue, saver); } gc.Pop(true); if (bombIfNothing) { var test = ExpressionToCPP.GetExpression(Expression.Not(valueWasSeen), gc, cc, container); gc.Add(new Statements.StatementThrowIfTrue(test, string.Format("First/Last predicate executed on a null sequence: {0}", queryModel.ToString()))); } // // Finally, we need the new expression. For this we basically just ask for the translated expression. We // also add a substitution for later on for more complex expressions. // var firstlastValue = cc.LoopVariable; cc.Add(indexExpr.RawValue, indexSeen); Debug.WriteLine("First/Last: {0} for QM {1}", indexSeen.ToString(), queryModel.ToString()); // Reset the expression we are looking at in the loop. var newIndexExpr = firstlastValue.ReplaceSubExpression(indexExpr.AsExpression(), indexSeen); cc.SetLoopVariable(newIndexExpr, indexSeen); if (cacheResult) { // // Set the default value // var actualValue = DeclarableParameter.CreateDeclarableParameterExpression(cc.LoopVariable.Type); actualValue.SetInitialValue("0"); // // If everything went well, then we can do the assignment. Otherwise, we leave // it as above (having the default value). // gc.Add(new Statements.StatementFilter(valueWasSeen)); gc.Add(new Statements.StatementAssign(actualValue, ExpressionToCPP.GetExpression(firstlastValue, gc, cc, container))); gc.Pop(); return actualValue; } else { // No need to cache the result - so no need to add extra code. return newIndexExpr; } }