private IExpression CompileExpression(Queue <Token> tokens, OperatorPrecedence minimumPrecedence, IList <string> variables, bool isWithinFunction) { if (tokens == null) { throw new ArgumentNullException("tokens", "You must call Tokenise before compiling"); } IExpression leftHandSide = null; var currentToken = tokens.PeekOrDefault(); Token previousToken = null; while (currentToken != null) { Func <IExpression[], IDictionary <string, object>, object> function = null; IOperator op = null; if (_registeredOperators.TryGetValue(currentToken.CurrentToken, out op)) // Are we an IOperator? { var precedence = op.GetPrecedence(previousToken); if (precedence > minimumPrecedence) { tokens.Dequeue(); if (!op.CanGetCaptiveTokens(previousToken, currentToken, tokens)) { // Do it anyway to update the list of tokens op.GetCaptiveTokens(previousToken, currentToken, tokens); break; } else { IExpression rightHandSide = null; var captiveTokens = op.GetCaptiveTokens(previousToken, currentToken, tokens); if (captiveTokens.Length > 1) { var innerTokens = op.GetInnerCaptiveTokens(captiveTokens); rightHandSide = CompileExpression(new Queue <Token>(innerTokens), OperatorPrecedence.Minimum, variables, isWithinFunction); currentToken = captiveTokens[captiveTokens.Length - 1]; } else { rightHandSide = CompileExpression(tokens, precedence, variables, isWithinFunction); // We are at the end of an expression so fake it up. currentToken = new Token(")", -1); } leftHandSide = op.BuildExpression(previousToken, new[] { leftHandSide, rightHandSide }, _options); } } else { break; } } else if (_registeredFunctions.TryGetValue(currentToken.CurrentToken, out function)) // or an IFunction? { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); var expressions = new List <IExpression>(); var captiveTokens = new Queue <Token>(); var parenCount = 0; tokens.Dequeue(); // Loop through the list of tokens and split by ParameterSeparator character while (tokens.Count > 0) { var nextToken = tokens.Dequeue(); if (string.Equals(nextToken.CurrentToken, "(", StringComparison.Ordinal)) { parenCount++; } else if (string.Equals(nextToken.CurrentToken, ")", StringComparison.Ordinal)) { parenCount--; } if (!(parenCount == 1 && nextToken.CurrentToken == "(") && !(parenCount == 0 && nextToken.CurrentToken == ")")) { captiveTokens.Enqueue(nextToken); } if (parenCount == 0 && captiveTokens.Any()) { expressions.Add(CompileExpression(captiveTokens, minimumPrecedence: OperatorPrecedence.Minimum, variables: variables, isWithinFunction: true)); captiveTokens.Clear(); } else if (string.Equals(nextToken.CurrentToken, ParameterSeparator.ToString(), StringComparison.Ordinal) && parenCount == 1) { // TODO: Should we expect expressions to be null??? expressions.Add(CompileExpression(captiveTokens, minimumPrecedence: 0, variables: variables, isWithinFunction: true)); captiveTokens.Clear(); } if (parenCount <= 0) { break; } } leftHandSide = new FunctionExpression(currentToken.CurrentToken, function, expressions.ToArray()); } else if (currentToken.CurrentToken.IsNumeric()) // Or a number { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); tokens.Dequeue(); int intValue = 0; decimal decimalValue = 0.0M; double doubleValue = 0.0; float floatValue = 0.0f; long longValue = 0; if (int.TryParse(currentToken.CurrentToken, out intValue)) { leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Integer, intValue); } else if (decimal.TryParse(currentToken.CurrentToken, out decimalValue)) { leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Decimal, decimalValue); } else if (double.TryParse(currentToken.CurrentToken, out doubleValue)) { leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Double, doubleValue); } else if (float.TryParse(currentToken.CurrentToken, out floatValue)) { leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Float, floatValue); } else if (long.TryParse(currentToken.CurrentToken, out longValue)) { leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Long, longValue); } } else if (currentToken.CurrentToken.StartsWith("[") && currentToken.CurrentToken.EndsWith("]")) // or a variable? { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); tokens.Dequeue(); string variableName = currentToken.CurrentToken.Replace("[", "").Replace("]", ""); leftHandSide = new VariableExpression(variableName); if (!variables.Contains(variableName, _stringComparer)) { variables.Add(variableName); } } else if (string.Equals(currentToken.CurrentToken, "true", StringComparison.OrdinalIgnoreCase)) // or a boolean? { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); tokens.Dequeue(); leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Boolean, true); } else if (string.Equals(currentToken.CurrentToken, "false", StringComparison.OrdinalIgnoreCase)) { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); tokens.Dequeue(); leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Boolean, false); } else if (string.Equals(currentToken.CurrentToken, "null", StringComparison.OrdinalIgnoreCase)) // or a null? { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); tokens.Dequeue(); leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Null, null); } else if (currentToken.CurrentToken.StartsWith(DateSeparator.ToString()) && currentToken.CurrentToken.EndsWith(DateSeparator.ToString())) // or a date? { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); tokens.Dequeue(); string dateToken = currentToken.CurrentToken.Replace(DateSeparator.ToString(), ""); DateTime date = DateTime.MinValue; // If we can't parse the date let's check for some known tags. if (!DateTime.TryParse(dateToken, out date)) { if (string.Equals("TODAY", dateToken, StringComparison.OrdinalIgnoreCase)) { date = DateTime.Today; } else if (string.Equals("NOW", dateToken, StringComparison.OrdinalIgnoreCase)) { date = DateTime.Now; } else { throw new UnrecognisedTokenException(dateToken); } } leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.DateTime, date); } else if ((currentToken.CurrentToken.StartsWith("'") && currentToken.CurrentToken.EndsWith("'")) || (currentToken.CurrentToken.StartsWith("\"") && currentToken.CurrentToken.EndsWith("\""))) { this.CheckForExistingParticipant(leftHandSide, currentToken, isWithinFunction); tokens.Dequeue(); leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.String, CleanString(currentToken.CurrentToken.Substring(1, currentToken.Length - 2))); } else if (string.Equals(currentToken.CurrentToken, ParameterSeparator.ToString(), StringComparison.Ordinal)) // Make sure we ignore the parameter separator { // TODO should we throw an exception if we are not within a function? if (!isWithinFunction) { throw new ExpressiveException($"Unexpected token '{currentToken}'"); } tokens.Dequeue(); //throw new InvalidOperationException("Unrecognised token '" + currentToken + "'"); //if (!string.Equals(currentToken, ParameterSeparator.ToString(), StringComparison.Ordinal)) // Make sure we ignore the parameter separator //{ // currentToken = CleanString(currentToken); // leftHandSide = new ConstantValueExpression(ConstantValueExpressionType.Unknown, currentToken); //} } else { tokens.Dequeue(); throw new UnrecognisedTokenException(currentToken.CurrentToken); } previousToken = currentToken; currentToken = tokens.PeekOrDefault(); } return(leftHandSide); }