// BaseType: FuncIdent | NUMBER | QUOTE - note certain types are restricted in expressions private void MatchBaseType(out IExpr result) { if (MatchFuncIDent(out result)) return; switch (curToken.Type) { case TokenTypes.NUMBER: result = new ConstantDecimal(curToken.Value); break; case TokenTypes.DATETIME: result = new ConstantDateTime(curToken.Value); break; case TokenTypes.DOUBLE: result = new ConstantDouble(curToken.Value); break; case TokenTypes.INTEGER: result = new ConstantInteger(curToken.Value); break; case TokenTypes.QUOTE: result = new ConstantString(curToken.Value); break; default: throw new ParserException("Constant or Identifier expected but not found. Found '" + curToken.Value + "' At column " + Convert.ToString(curToken.StartCol)); } curToken = tokens.Extract(); 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; }
private void MatchExprUnary(out IExpr result) { TokenTypes t; // remember the type t = curToken.Type; if (t == TokenTypes.PLUS || t == TokenTypes.MINUS) { curToken = tokens.Extract(); } MatchExprParen(out result); if (t == TokenTypes.MINUS) { if (result.GetTypeCode() == TypeCode.Decimal) result = new FunctionUnaryMinusDecimal(result); else if (result.GetTypeCode() == TypeCode.Int32) result = new FunctionUnaryMinusInteger(result); else result = new FunctionUnaryMinus(result); } }
// Factor: ( Expr ) | BaseType | - BaseType | - ( Expr ) private void MatchExprParen(out IExpr result) { // Match- ( Expr ) if (curToken.Type == TokenTypes.LPAREN) { // trying to match ( Expr ) curToken = tokens.Extract(); MatchExprAndOr(out result); if (curToken.Type != TokenTypes.RPAREN) throw new ParserException("')' expected but not found. Found '" + curToken.Value + "' At column " + Convert.ToString(curToken.StartCol)); curToken = tokens.Extract(); } else MatchBaseType(out result); }
// TermRhs: MultDivOperator Factor TermRhs private void MatchExprMultDiv(out IExpr result) { TokenTypes t; // remember the type IExpr lhs; MatchExprExp(out lhs); result = lhs; // in case we get no matches while ((t = curToken.Type) == TokenTypes.FORWARDSLASH || t == TokenTypes.STAR || t == TokenTypes.MODULUS) { curToken = tokens.Extract(); IExpr rhs; MatchExprExp(out rhs); bool bDecimal = (rhs.GetTypeCode() == TypeCode.Decimal && lhs.GetTypeCode() == TypeCode.Decimal); switch (t) { case TokenTypes.FORWARDSLASH: if (bDecimal) result = new FunctionDivDecimal(lhs, rhs); else result = new FunctionDiv(lhs, rhs); break; case TokenTypes.STAR: if (bDecimal) result = new FunctionMultDecimal(lhs, rhs); else result = new FunctionMult(lhs, rhs); break; case TokenTypes.MODULUS: result = new FunctionModulus(lhs, rhs); break; } lhs = result; // in case continue in the loop } }
// TermRhs: ExpOperator Factor TermRhs private void MatchExprExp(out IExpr result) { IExpr lhs; MatchExprUnary(out lhs); if (curToken.Type == TokenTypes.EXP) { curToken = tokens.Extract(); IExpr rhs; MatchExprUnary(out rhs); result = new FunctionExp(lhs, rhs); } else result = lhs; }
// ExprRelop: private void MatchExprRelop(out IExpr result) { TokenTypes t; // remember the type IExpr lhs; MatchExprAddSub(out lhs); result = lhs; // in case we get no matches while ((t = curToken.Type) == TokenTypes.EQUAL || t == TokenTypes.NOTEQUAL || t == TokenTypes.GREATERTHAN || t == TokenTypes.GREATERTHANOREQUAL || t == TokenTypes.LESSTHAN || t == TokenTypes.LESSTHANOREQUAL) { curToken = tokens.Extract(); IExpr rhs; MatchExprAddSub(out rhs); switch(t) { case TokenTypes.EQUAL: result = new FunctionRelopEQ(lhs, rhs); break; case TokenTypes.NOTEQUAL: result = new FunctionRelopNE(lhs, rhs); break; case TokenTypes.GREATERTHAN: result = new FunctionRelopGT(lhs, rhs); break; case TokenTypes.GREATERTHANOREQUAL: result = new FunctionRelopGTE(lhs, rhs); break; case TokenTypes.LESSTHAN: result = new FunctionRelopLT(lhs, rhs); break; case TokenTypes.LESSTHANOREQUAL: result = new FunctionRelopLTE(lhs, rhs); break; } lhs = result; // in case we continue the loop } }
// ExprAddSub: PlusMinusOperator Term ExprRhs private void MatchExprAddSub(out IExpr result) { TokenTypes t; // remember the type IExpr lhs; MatchExprMultDiv(out lhs); result = lhs; // in case we get no matches while ((t = curToken.Type) == TokenTypes.PLUS || t == TokenTypes.PLUSSTRING || t == TokenTypes.MINUS) { curToken = tokens.Extract(); IExpr rhs; MatchExprMultDiv(out rhs); TypeCode lt = lhs.GetTypeCode(); TypeCode rt = rhs.GetTypeCode(); bool bDecimal = (rt == TypeCode.Decimal && lt == TypeCode.Decimal); bool bInt32 = (rt == TypeCode.Int32 && lt == TypeCode.Int32); bool bString = (rt == TypeCode.String || lt == TypeCode.String); switch(t) { case TokenTypes.PLUSSTRING: result = new FunctionPlusString(lhs, rhs); break; case TokenTypes.PLUS: if (bDecimal) result = new FunctionPlusDecimal(lhs, rhs); else if (bString) result = new FunctionPlusString(lhs, rhs); else if (bInt32) result = new FunctionPlusInt32(lhs, rhs); else result = new FunctionPlus(lhs, rhs); break; case TokenTypes.MINUS: if (bDecimal) result = new FunctionMinusDecimal(lhs, rhs); else if (bString) throw new ParserException("'-' operator works only on numbers." + " At column " + Convert.ToString(curToken.StartCol)); else if (bInt32) result = new FunctionMinusInt32(lhs, rhs); else result = new FunctionMinus(lhs, rhs); break; } lhs = result; // in case continue in the loop } }
private void MatchExprNot(out IExpr result) { TokenTypes t; // remember the type t = curToken.Type; if (t == TokenTypes.NOT) { curToken = tokens.Extract(); } MatchExprRelop(out result); if (t == TokenTypes.NOT) { if (result.GetTypeCode() != TypeCode.Boolean) throw new ParserException("NOT requires boolean expression." + " At column " + Convert.ToString(curToken.StartCol)); result = new FunctionNot(result); } }
// ExprAndOr: private void MatchExprAndOr(out IExpr result) { TokenTypes t; // remember the type IExpr lhs; MatchExprNot(out lhs); result = lhs; // in case we get no matches while ((t = curToken.Type) == TokenTypes.AND || t == TokenTypes.OR) { curToken = tokens.Extract(); IExpr rhs; MatchExprNot(out rhs); bool bBool = (rhs.GetTypeCode() == TypeCode.Boolean && lhs.GetTypeCode() == TypeCode.Boolean); if (!bBool) throw new ParserException("AND/OR operations require both sides to be boolean expressions." + " At column " + Convert.ToString(curToken.StartCol)); switch(t) { case TokenTypes.AND: result = new FunctionAnd(lhs, rhs); break; case TokenTypes.OR: result = new FunctionOr(lhs, rhs); break; } lhs = result; // in case we have more AND/OR s } }
/// <summary> /// Returns a parsed DPL instance. /// </summary> /// <param name="reader">The TextReader value to be parsed.</param> /// <returns>A parsed Program instance.</returns> private IExpr ParseExpr(TextReader reader) { IExpr result=null; Lexer lexer = new Lexer(reader); tokens = lexer.Lex(); if (tokens.Peek().Type == TokenTypes.EQUAL) { tokens.Extract(); // skip over the equal curToken = tokens.Extract(); // set up the first token MatchExprAndOr(out result); // start with lowest precedence and work up } if (curToken.Type != TokenTypes.EOF) throw new ParserException("End of expression expected." + " At column " + Convert.ToString(curToken.StartCol)); return result; }
public void Push(Token token) { tokens.Insert(0, token); }
public void Add(Token token) { tokens.Add(token); }