/// <summary> /// Run a sub-query for the user. The sub-query is a full-blown expression that /// must usually run in its own loop (or similar). /// </summary> /// <remarks> /// The query visitor must be re-invoked - this ends up being a full-blown /// parsing. /// </remarks> /// <param name="expression"></param> /// <returns></returns> protected override Expression VisitSubQuery(SubQueryExpression expression) { if (MEFContainer == null) { throw new InvalidOperationException("MEFContainer can't be null if we need to analyze a sub query!"); } QueryVisitor qv = new QueryVisitor(GeneratedCode, CodeContext, MEFContainer); MEFContainer.SatisfyImportsOnce(qv); /// /// Run it - since this result is out of this loop, we pop-back-out when done. /// var scope = GeneratedCode.CurrentScope; GeneratedCode.SetCurrentScopeAsResultScope(); qv.VisitQueryModel(expression.QueryModel); // // And the main index that was looked at we need to cache ourselves. // if (qv.MainIndexScope != null) { CodeContext.CacheVariableToEliminate(qv.MainIndexScope); } /// /// Two possible results from the sub-expression query, and how we proceed depends /// on what happened in the sub query /// /// 1. <returns a value> - an operator like Count() comes back from the sequence. /// it will get used in some later sequence (like # of jets in each event). So, /// we need to make sure it is declared and kept before it is used. The # that comes /// back needs to be used outside the scope we are sitting in - the one that we were at /// when we started this. Since this is a sub-query expression, the result isn't the final /// result, so we need to reset it so no one notices it. /// 2. <return a sequence> - this is weird - What we are actually doing here is putting the /// sequence into code. So the loop variable has been updated with the new sequence iterator /// value. But there isn't really a result! So the result will be null... /// var r = GeneratedCode.ResultValue; if (r != null) { GeneratedCode.CurrentScope = scope; if (r is IDeclaredParameter) { GeneratedCode.Add(r as IDeclaredParameter, false); } GeneratedCode.ResetResult(); return(r); } // // The fact that we returned null means we are dealing with a // sequence. There really is no translated version of this expression // in that case - so we will return null. If someone above is depending // on doing something with it they are going to run into some // trouble! return(null); }
/// <summary> /// Cache the result of a query model into a function. /// </summary> /// <param name="queryModel"></param> /// <param name="qmSource"></param> private void VisitQueryModelCache(QueryModel queryModel, IQMFunctionSource qmSource) { // If we already have the answer for this cache, then we should just re-call the routine. if (qmSource.StatementBlock != null) { Debug.WriteLine("Using previously cached QM result"); GenerateQMFunctionCall(qmSource); return; } Debug.WriteLine("Cache: Gathering Data"); Debug.Indent(); // Since we don't have it cached, we need to re-run things, and carefully watch for // everything new that shows up. What shows up will be what we declare as the function // body. var currentScope = _codeEnv.CurrentScope; var topLevelStatement = new StatementInlineBlock(); _codeEnv.Add(topLevelStatement); _codeEnv.SetCurrentScopeAsResultScope(); // If this variable has been cached, then return it. Otherwise, mark the cache as filled. _codeEnv.Add(new StatementFilter(qmSource.CacheVariableGood)); _codeEnv.Add(new StatementReturn(qmSource.CacheVariable)); _codeEnv.Pop(); _codeEnv.Add(new StatementAssign(qmSource.CacheVariableGood, new ValSimple("true", typeof(bool), null))); // Now, run the code to process the query model! VisitQueryModelNoCache(queryModel); // The result is treated differently depending on it being a sequence or a single value. if (qmSource.IsSequence) { // Push the good values into our cache object. if (!(_codeContext.LoopIndexVariable is IDeclaredParameter)) { throw new InvalidOperationException("Can't deal with anythign that isn't a loop var"); } _codeEnv.Add(new StatementRecordIndicies(ExpressionToCPP.GetExpression(_codeContext.LoopVariable, _codeEnv, _codeContext, MEFContainer), qmSource.CacheVariable)); // Remember what the loop index variable is, as we are going to need it when we generate the return function! qmSource.SequenceVariable(_codeContext.LoopIndexVariable, _codeContext.LoopVariable); } else { // This is a specific result. Save just the result and return it. // Grab the result, cache it, and return it. var rtnExpr = ExpressionToCPP.GetExpression(_codeEnv.ResultValue, _codeEnv, _codeContext, MEFContainer); topLevelStatement.Add(new StatementAssign(qmSource.CacheVariable, rtnExpr)); // If the return is a declared parameter, then it must be actually defined somewhere (we normally don't). var declParam = _codeEnv.ResultValue as IDeclaredParameter; if (declParam != null) { topLevelStatement.Add(declParam, false); } } // Always return the proper value... topLevelStatement.Add(new StatementReturn(qmSource.CacheVariable)); // Now extract the block of code and put it in the function block. _codeEnv.CurrentScope = currentScope; qmSource.SetCodeBody(topLevelStatement); // Reset our state and remove the function code. And put in the function call in its place. _codeEnv.Remove(topLevelStatement); GenerateQMFunctionCall(qmSource); Debug.Unindent(); }