List <GroupEntry> _NestedGroup; // group one hierarchy below internal GroupEntry(Grouping g, Sorting s, int start) { _Group = g; _Sort = s; _StartRow = start; _EndRow = -1; _NestedGroup = new List <GroupEntry>(); // Check to see if grouping and sorting are the same if (g == null || s == null) { return; // nothing to check if either is null } if (s.Items.Count != g.GroupExpressions.Items.Count) { return; } for (int i = 0; i < s.Items.Count; i++) { SortBy sb = s.Items[i] as SortBy; if (sb.Direction == SortDirectionEnum.Descending) { return; // TODO we could optimize this } FunctionField ff = sb.SortExpression.Expr as FunctionField; if (ff == null || ff.GetTypeCode() != TypeCode.String) { return; } GroupExpression ge = g.GroupExpressions.Items[i] as GroupExpression; FunctionField ff2 = ge.Expression.Expr as FunctionField; if (ff2 == null || ff.Fld != ff2.Fld) { return; } } _Sort = null; // we won't need to sort since the groupby will handle it correctly }
// Handle parsing of function in final pass override internal void FinalPass() { base.FinalPass(); _Value.FinalPass(); if (this.DataElementName == null && this.Name == null) { // no name or dataelementname; try using expression FunctionField ff = _Value.Expr as FunctionField; if (ff != null && ff.Fld != null) { this.DataElementName = ff.Fld.DataField; } } if (_ToggleImage != null) { _ToggleImage.FinalPass(); } if (_HideDuplicates != null) { object o = OwnerReport.LUAggrScope[_HideDuplicates]; if (o == null) { OwnerReport.rl.LogError(4, "HideDuplicate '" + _HideDuplicates + "' is not a Group or DataSet name. It will be ignored."); _HideDuplicates = null; } else if (o is Grouping) { Grouping g = o as Grouping; g.AddHideDuplicates(this); } else if (o is DataSetDefn) { DataSetDefn ds = o as DataSetDefn; ds.AddHideDuplicates(this); } } return; }
// FuncIDent: IDENTIFIER ( [Expr] [, Expr]*) | IDENTIFIER private bool MatchFuncIDent(out IExpr result) { IExpr e; string fullname; // will hold the full name string method; // will hold method name or second part of name string firstPart; // will hold the collection name string thirdPart; // will hold third part of name bool bOnePart; // simple name: no ! or . in name result = null; if (curToken.Type != TokenTypes.IDENTIFIER) return false; // Disentangle method calls from collection references method = fullname = curToken.Value; curToken = tokens.Extract(); // Break the name into parts char[] breakChars = new char[] {'!', '.'}; int posBreak = method.IndexOfAny(breakChars); if (posBreak > 0) { bOnePart = false; firstPart = method.Substring(0, posBreak); method = method.Substring(posBreak+1); // rest of expression } else { bOnePart = true; firstPart = method; } posBreak = method.IndexOf('.'); if (posBreak > 0) { thirdPart = method.Substring(posBreak+1); // rest of expression method = method.Substring(0, posBreak); } else thirdPart = null; if (curToken.Type != TokenTypes.LPAREN) switch (firstPart) { case "Fields": Field f = idLookup.LookupField(method); if (f == null && !this._InAggregate) throw new ParserException("Field '" + method + "' not found."); if (thirdPart == null || thirdPart == "Value") { if (f == null) { FunctionField ff; result = ff = new FunctionField(method); this._FieldResolve.Add(ff); } else result = new FunctionField(f); } else if (thirdPart == "IsMissing") { if (f == null) { FunctionField ff; result = ff = new FunctionFieldIsMissing(method); this._FieldResolve.Add(ff); } else result = new FunctionFieldIsMissing(f); } else throw new ParserException("Field '" + method + "' only supports 'Value' and 'IsMissing' properties."); return true; case "Parameters": // see ResolveParametersMethod for resolution of MultiValue parameter function reference ReportParameter p = idLookup.LookupParameter(method); if (p == null) throw new ParserException("Report parameter '" + method + "' not found."); int ci = thirdPart == null? -1: thirdPart.IndexOf(".Count"); if (ci > 0) thirdPart = thirdPart.Substring(0, ci); FunctionReportParameter r; if (thirdPart == null || thirdPart == "Value") r = new FunctionReportParameter(p); else if (thirdPart == "Label") r = new FunctionReportParameterLabel(p); else throw new ParserException("Parameter '" + method + "' only supports 'Value' and 'Label' properties."); if (ci > 0) r.SetParameterMethod("Count", null); result = r; return true; case "ReportItems": Textbox t = idLookup.LookupReportItem(method); if (t == null) throw new ParserException("ReportItem '" + method + "' not found."); if (thirdPart != null && thirdPart != "Value") throw new ParserException("ReportItem '" + method + "' only supports 'Value' property."); result = new FunctionTextbox(t, idLookup.ExpressionName); return true; case "Globals": e = idLookup.LookupGlobal(method); if (e == null) throw new ParserException("Globals '" + method + "' not found."); result = e; return true; case "User": e = idLookup.LookupUser(method); if (e == null) throw new ParserException("User variable '" + method + "' not found."); result = e; return true; case "Recursive": // Only valid for some aggregate functions result = new IdentifierKey(IdentifierKeyEnum.Recursive); return true; case "Simple": // Only valid for some aggregate functions result = new IdentifierKey(IdentifierKeyEnum.Simple); return true; default: if (!bOnePart) throw new ParserException(string.Format("'{0}' is an unknown identifer.", fullname)); switch (method.ToLower()) // lexer should probably mark these { case "true": case "false": result = new ConstantBoolean(method.ToLower()); break; default: // usually this is enum that will be used in an aggregate result = new Identifier(method); break; } return true; } // We've got an function reference curToken = tokens.Extract(); // get rid of '(' // Got a function now obtain the arguments int argCount=0; bool isAggregate = IsAggregate(method, bOnePart); if (_NoAggregate && isAggregate) throw new ParserException("Aggregate function '" + method + "' cannot be used within a Grouping expression."); if (_InAggregate && isAggregate) throw new ParserException("Aggregate function '" + method + "' cannot be nested in another aggregate function."); _InAggregate = isAggregate; if (_InAggregate) _FieldResolve = new List<FunctionField>(); List<IExpr> largs = new List<IExpr>(); while(true) { if (curToken.Type == TokenTypes.RPAREN) { // We've got our function curToken = tokens.Extract(); break; } if (argCount == 0) { // don't need to do anything } else if (curToken.Type == TokenTypes.COMMA) { curToken = tokens.Extract(); } else throw new ParserException("Invalid function arguments. Found '" + curToken.Value + "' At column " + Convert.ToString(curToken.StartCol)); MatchExprAndOr(out e); if (e == null) throw new ParserException("Expecting ',' or ')'. Found '" + curToken.Value + "' At column " + Convert.ToString(curToken.StartCol)); largs.Add(e); argCount++; } if (_InAggregate) { ResolveFields(method, this._FieldResolve, largs); _FieldResolve = null; _InAggregate = false; } IExpr[] args = largs.ToArray(); object scope; bool bSimple; if (!bOnePart) { result = (firstPart == "Parameters")? ResolveParametersMethod(method, thirdPart, args): ResolveMethodCall(fullname, args); // throw exception when fails } else switch(method.ToLower()) { case "iif": if (args.Length != 3) throw new ParserException("iff function requires 3 arguments." + " At column " + Convert.ToString(curToken.StartCol)); // We allow any type for the first argument; it will get converted to boolean at runtime // if (args[0].GetTypeCode() != TypeCode.Boolean) // throw new ParserException("First argument to iif function must be boolean." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionIif(args[0], args[1], args[2]); break; case "choose": if (args.Length <= 2) throw new ParserException("Choose function requires at least 2 arguments." + " At column " + Convert.ToString(curToken.StartCol)); switch (args[0].GetTypeCode()) { case TypeCode.Double: case TypeCode.Single: case TypeCode.Int32: case TypeCode.Decimal: case TypeCode.Int16: case TypeCode.Int64: break; default: throw new ParserException("First argument to Choose function must be numeric." + " At column " + Convert.ToString(curToken.StartCol)); } result = new FunctionChoose(args); break; case "switch": if (args.Length <= 2) throw new ParserException("Switch function requires at least 2 arguments." + " At column " + Convert.ToString(curToken.StartCol)); if (args.Length % 2 != 0) throw new ParserException("Switch function must have an even number of arguments." + " At column " + Convert.ToString(curToken.StartCol)); for (int i=0; i < args.Length; i = i+2) { if (args[i].GetTypeCode() != TypeCode.Boolean) throw new ParserException("Switch function must have a boolean expression every other argument." + " At column " + Convert.ToString(curToken.StartCol)); } result = new FunctionSwitch(args); break; case "format": if (args.Length > 2 || args.Length < 1) throw new ParserException("Format function requires 2 arguments." + " At column " + Convert.ToString(curToken.StartCol)); if (args.Length == 1) { result = new FunctionFormat(args[0], new ConstantString("")); } else { if (args[1].GetTypeCode() != TypeCode.String) throw new ParserException("Second argument to Format function must be a string." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionFormat(args[0], args[1]); } break; case "fields": if (args.Length != 1) throw new ParserException("Fields collection requires exactly 1 argument." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionFieldCollection(idLookup.Fields, args[0]); if (curToken.Type == TokenTypes.DOT) { // user placed "." TODO: generalize this code curToken = tokens.Extract(); // skip past dot operator if (curToken.Type == TokenTypes.IDENTIFIER && curToken.Value.ToLowerInvariant() == "value") curToken = tokens.Extract(); // only support "value" property for now else throw new ParserException(curToken.Value + " is not a known property for Fields." + " At column " + Convert.ToString(curToken.StartCol)); } break; case "parameters": if (args.Length != 1) throw new ParserException("Parameters collection requires exactly 1 argument." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionParameterCollection(idLookup.Parameters, args[0]); if (curToken.Type == TokenTypes.DOT) { // user placed "." curToken = tokens.Extract(); // skip past dot operator if (curToken.Type == TokenTypes.IDENTIFIER && curToken.Value.ToLowerInvariant() == "value") curToken = tokens.Extract(); // only support "value" property for now else throw new ParserException(curToken.Value + " is not a known property for Fields." + " At column " + Convert.ToString(curToken.StartCol)); } break; case "reportitems": if (args.Length != 1) throw new ParserException("ReportItems collection requires exactly 1 argument." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionReportItemCollection(idLookup.ReportItems, args[0]); if (curToken.Type == TokenTypes.DOT) { // user placed "." curToken = tokens.Extract(); // skip past dot operator if (curToken.Type == TokenTypes.IDENTIFIER && curToken.Value.ToLowerInvariant() == "value") curToken = tokens.Extract(); // only support "value" property for now else throw new ParserException(curToken.Value + " is not a known property for Fields." + " At column " + Convert.ToString(curToken.StartCol)); } break; case "globals": if (args.Length != 1) throw new ParserException("Globals collection requires exactly 1 argument." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionGlobalCollection(idLookup.Globals, args[0]); break; case "user": if (args.Length != 1) throw new ParserException("User collection requires exactly 1 argument." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionUserCollection(idLookup.User, args[0]); break; case "sum": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrSum aggrFS = new FunctionAggrSum(_DataCache, args[0], scope); aggrFS.LevelCheck = bSimple; result = aggrFS; break; case "avg": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrAvg aggrFA = new FunctionAggrAvg(_DataCache, args[0], scope); aggrFA.LevelCheck = bSimple; result = aggrFA; break; case "min": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrMin aggrFMin = new FunctionAggrMin(_DataCache, args[0], scope); aggrFMin.LevelCheck = bSimple; result = aggrFMin; break; case "max": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrMax aggrFMax = new FunctionAggrMax(_DataCache, args[0], scope); aggrFMax.LevelCheck = bSimple; result = aggrFMax; break; case "first": scope = ResolveAggrScope(args, 2, out bSimple); result = new FunctionAggrFirst(_DataCache, args[0], scope); break; case "last": scope = ResolveAggrScope(args, 2, out bSimple); result = new FunctionAggrLast(_DataCache, args[0], scope); break; case "next": scope = ResolveAggrScope(args, 2, out bSimple); result = new FunctionAggrNext(_DataCache, args[0], scope); break; case "previous": scope = ResolveAggrScope(args, 2, out bSimple); result = new FunctionAggrPrevious(_DataCache, args[0], scope); break; case "level": scope = ResolveAggrScope(args, 1, out bSimple); result = new FunctionAggrLevel(scope); break; case "aggregate": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrArray aggr = new FunctionAggrArray(_DataCache, args[0], scope); aggr.LevelCheck = bSimple; result = aggr; break; case "count": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrCount aggrFC = new FunctionAggrCount(_DataCache, args[0], scope); aggrFC.LevelCheck = bSimple; result = aggrFC; break; case "countrows": scope = ResolveAggrScope(args, 1, out bSimple); FunctionAggrCountRows aggrFCR = new FunctionAggrCountRows(scope); aggrFCR.LevelCheck = bSimple; result = aggrFCR; break; case "countdistinct": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrCountDistinct aggrFCD = new FunctionAggrCountDistinct(_DataCache, args[0], scope); aggrFCD.LevelCheck = bSimple; result = aggrFCD; break; case "rownumber": scope = ResolveAggrScope(args, 1, out bSimple); IExpr texpr = new ConstantDouble("0"); result = new FunctionAggrRvCount(_DataCache, texpr, scope); break; case "runningvalue": if (args.Length < 2 || args.Length > 3) throw new ParserException("RunningValue takes 2 or 3 arguments." + " At column " + Convert.ToString(curToken.StartCol)); string aggrFunc = args[1].EvaluateString(null, null); if (aggrFunc == null) throw new ParserException("RunningValue 'Function' argument is invalid." + " At column " + Convert.ToString(curToken.StartCol)); scope = ResolveAggrScope(args, 3, out bSimple); switch(aggrFunc.ToLower()) { case "sum": result = new FunctionAggrRvSum(_DataCache, args[0], scope); break; case "avg": result = new FunctionAggrRvAvg(_DataCache, args[0], scope); break; case "count": result = new FunctionAggrRvCount(_DataCache, args[0], scope); break; case "max": result = new FunctionAggrRvMax(_DataCache, args[0], scope); break; case "min": result = new FunctionAggrRvMin(_DataCache, args[0], scope); break; case "stdev": result = new FunctionAggrRvStdev(_DataCache, args[0], scope); break; case "stdevp": result = new FunctionAggrRvStdevp(_DataCache, args[0], scope); break; case "var": result = new FunctionAggrRvVar(_DataCache, args[0], scope); break; case "varp": result = new FunctionAggrRvVarp(_DataCache, args[0], scope); break; default: throw new ParserException("RunningValue function '" + aggrFunc + "' is not supported. At column " + Convert.ToString(curToken.StartCol)); } break; case "stdev": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrStdev aggrSDev = new FunctionAggrStdev(_DataCache, args[0], scope); aggrSDev.LevelCheck = bSimple; result = aggrSDev; break; case "stdevp": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrStdevp aggrSDevP = new FunctionAggrStdevp(_DataCache, args[0], scope); aggrSDevP.LevelCheck = bSimple; result = aggrSDevP; break; case "var": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrVar aggrVar = new FunctionAggrVar(_DataCache, args[0], scope); aggrVar.LevelCheck = bSimple; result = aggrVar; break; case "varp": scope = ResolveAggrScope(args, 2, out bSimple); FunctionAggrVarp aggrVarP = new FunctionAggrVarp(_DataCache, args[0], scope); aggrVarP.LevelCheck = bSimple; result = aggrVarP; break; default: result = ResolveMethodCall(fullname, args); // through exception when fails break; } return true; }