public Either <ParsingError, CellValueCalculator> TryApplyBinaryOperator(
            string operatorChars,
            CellValueCalculator secondArgument,
            FormulaParameters parameters)
        {
            if ((operatorChars == "&&" || operatorChars == "||") &&
                (ResultType != typeof(bool) || secondArgument.ResultType != typeof(bool)))
            {
                return(Left(new ParsingError($"Operator '{operatorChars}' cannot be applied to operands of type '{ResultType.Name}' and '{secondArgument.ResultType}'")));
            }

            var op        = KnownBinaryOperations[operatorChars];
            var localThis = this;

            return
                (from resultType in TryToDeduceResultingType(operatorChars, ResultType, secondArgument.ResultType)
                 from leftOperand in localThis.TryCastTo(resultType)
                 from rightOperand in secondArgument.TryCastTo(resultType)
                 select CombineCalculators(
                     op.ResultIsBoolean?typeof(bool) : resultType,
                     parameters,
                     op.Expression(leftOperand.CalculateExpression, rightOperand.CalculateExpression),
                     $"{leftOperand.CalculateExpression}{operatorChars}{rightOperand.CalculateExpression}",
                     leftOperand,
                     rightOperand));
        }
 private static Parser <CellValueCalculator> ArithmeticExpressionParser(
     Parser <CellValueCalculator> elementParser,
     FormulaParameters parameters,
     params string[] operationLexems)
 =>
 from elements in UniformList(elementParser, Operator(operationLexems))
 from combinedCalculator in FoldBinaryOperatorsList(elements, parameters).StopParsingIfFailed()
 select combinedCalculator;
 private static Parser <CellValueCalculator> FoldBinaryOperatorsList(
     ParsedUniformList <CellValueCalculator, string> operands,
     FormulaParameters parameters) =>
 operands
 .TailPairs
 .Aggregate(
     Right <ParsingError, CellValueCalculator>(operands.FirstElement),
     (valueCalculatorOrError, pair) =>
     valueCalculatorOrError.FlatMap(calculator => calculator.TryApplyBinaryOperator(pair.Link, pair.Right, parameters)))
 .Fold(
     error => Failure <CellValueCalculator>(textInput => textInput.MakeErrors(error, null)),
     Success);
        private static Parser <CellValueCalculator> CreateFormulaParser(
            Parser <CellValueCalculator> literal,
            Parser <CellValueCalculator> propertyAccessor,
            TryParse <IReadOnlyCollection <MethodInfo> > tryGetFunctionByName,
            FormulaParameters formulaParameters)
        {
            Parser <CellValueCalculator> resultingParser = null;

            // ReSharper disable once AccessToModifiedClosure
            var parameterList = from openingBrace in Lexem("(")
                                from arguments in Optional(UniformList(resultingParser, Lexem(",")))
                                from closingBrace in Lexem(")")
                                select arguments.Map(a => a.Elements).OrElse(Enumerable.Empty <CellValueCalculator>());

            TryParse <Maybe <IReadOnlyCollection <MethodInfo> > > tryGetFunctionOrConditionExpressionByName =
                (string name, out Maybe <IReadOnlyCollection <MethodInfo> > result) =>
            {
                result = tryGetFunctionByName(name, out var methodInfos) ? Some(methodInfos) : None;
                return(result.Map(_ => true).OrElse(name == "Iif" || name == "If"));
            };

            var functionCall = from functionName in Identifier
                               from lazyParameters in parameterList
                               from methodInfos in functionName.Try(tryGetFunctionOrConditionExpressionByName, n => $"Unknown function: '{n}'").StopParsingIfFailed()
                               let parameters = lazyParameters.ToArray()
                                                from methodInfo in PickFunctionOverload(functionName, methodInfos, parameters).StopParsingIfFailed()
                                                from arguments in methodInfo
                                                .Map(mi => MakeFunctionCallArguments(mi, parameters))
                                                .OrElse(() => MakeIifFunctionCallArguments(parameters))
                                                .StopParsingIfFailed()
                                                select CombineCalculators(
                methodInfo.Map(mi => mi.ReturnType).OrElse(() => arguments[1].Type),
                formulaParameters,
                methodInfo.Map(mi => (Expression)Expression.Call(mi, arguments))
                .OrElse(() => Expression.Condition(arguments[0], arguments[1], arguments[2])),
                $"{functionName}({string.Join(",", parameters.Select(c => c.CalculateExpression))})",
                parameters);

            // ReSharper disable once AccessToModifiedClosure
            var bracedExpression = from openingBrace in Lexem("(")
                                   from internalExpression in resultingParser
                                   from closingBrace in Lexem(")")
                                   select internalExpression;

            var multiplier = from optionalSign in Optional(Lexem("-", "+"))
                             from valueCalculator in literal.Or(bracedExpression).Or(functionCall).Or(propertyAccessor)
                             from adjustedCalculator in AsParser(valueCalculator.TryGiveSign(optionalSign.OrElse(string.Empty)))
                             select adjustedCalculator;

            resultingParser = CreateFormulaParserCore(multiplier, formulaParameters);

            return(resultingParser);
        }
 private static Expression GetPrecalculatedAggregatedValue(
     Type resultType,
     FormulaParameters parameters,
     string aggregatedValueKey)
 {
     // return (resultType)parameters.AggregatedValues[aggregatedValueKey]
     return(Expression.Convert(
                Expression.Property(
                    parameters.AggregatedValues,
                    "Item",
                    Expression.Constant(aggregatedValueKey, typeof(string))),
                resultType));
 }
        public ColumnFormulaBuilder(
            Type rowDataType,
            TryParse <Type> tryGetPropertyTypeByName,
            TryParse <IReadOnlyCollection <MethodInfo> > tryGetFunctionsByName)
        {
            var parameters = new FormulaParameters(rowDataType);

            _formulaCompiler = new FormulaExpressionsCompiler(parameters);

            var integerLiteral  = NumericLiteral(new TryParse <int>(int.TryParse));
            var decimalLiteral  = NumericLiteral(new TryParse <decimal>(decimal.TryParse));
            var dateTimeLiteral = ParsedQuotedLiteral(new TryParse <DateTime>(DateTime.TryParse));
            var stringLiteral   = QuotedLiteral.Select(Constant);
            var literal         = dateTimeLiteral.Or(stringLiteral).Or(integerLiteral).Or(decimalLiteral);

            var quotedPropertyName = from openingBracket in Lexem("[")
                                     from nameChars in Repeat(ch => ch != ']' && ch != '\r' && ch != '\n')
                                     from closingBraket in Lexem("]")
                                     select string.Join(
                string.Empty,
                nameChars
                .Where(ch => ch != ' ' && ch != '\t')
                .Select(ch => char.IsLetterOrDigit(ch) ? ch.ToString() : $"_char_{(int)ch}_"));

            var propertyName     = quotedPropertyName.Or(Identifier);
            var propertyAccessor = from name in propertyName
                                   from propertyType in name.Try(tryGetPropertyTypeByName, n => $"Unknown property: '{n}'").StopParsingIfFailed()
                                   select new CellValueCalculator(propertyType, Expression.Property(parameters.CurrentRow, name));

            var aggregatableExpression = CreateFormulaParser(literal, propertyAccessor, tryGetFunctionsByName, parameters);

            var aggregatedPropertyAccessor = from openingBracket in Lexem("[")
                                             from aggregationMethodText in Identifier
                                             from colon in Lexem(":")
                                             from aggregationMethod in aggregationMethodText.Try <AggregationMethod>(
                Enum.TryParse,
                m => $"Invalid aggregation method {m} specified: only '" +
                string.Join("', '", Enum.GetNames(typeof(AggregationMethod)) +
                            "' are supported")).StopParsingIfFailed()
                                             from calculator in aggregatableExpression
                                             from closingBraket in Lexem("]")
                                             select aggregationMethod == AggregationMethod.all
                                                ? calculator.All(_formulaCompiler)
                                                : calculator.FirstOrLast(aggregationMethod, _formulaCompiler);

            _formulaTextParser = CreateFormulaParser(
                literal,
                aggregatedPropertyAccessor.Or(propertyAccessor),
                tryGetFunctionsByName,
                parameters);
        }
        private static Parser <CellValueCalculator> CreateFormulaParserCore(
            Parser <CellValueCalculator> multiplier,
            FormulaParameters parameters)
        {
            var addend = ArithmeticExpressionParser(multiplier, parameters, "*", "/", "%");
            var comparableExpression     = ArithmeticExpressionParser(addend, parameters, "+", "-");
            var equatableExpression      = ArithmeticExpressionParser(comparableExpression, parameters, "<", "<=", ">", ">=");
            var bitwiseAndableExpression = ArithmeticExpressionParser(equatableExpression, parameters, "==", "!=");
            var bitwiseXorableExpression = ArithmeticExpressionParser(bitwiseAndableExpression, parameters, "&");
            var bitwiseOrableExpression  = ArithmeticExpressionParser(bitwiseXorableExpression, parameters, "^");
            var logicalAndableExpression = ArithmeticExpressionParser(bitwiseOrableExpression, parameters, "|");
            var logicalOrableExpression  = ArithmeticExpressionParser(logicalAndableExpression, parameters, "&&");

            return(ArithmeticExpressionParser(logicalOrableExpression, parameters, "||"));
        }
        public static CellValueCalculator CombineCalculators(
            Type resultType,
            FormulaParameters parameters,
            Expression calculateExpression,
            string aggregatedValueKey,
            params CellValueCalculator[] calculators)
        {
            var allAggregatedValueCalculators = calculators.Aggregate(
                AggregatedValueCalculators.Empty,
                (combinedCalculators, calculator) => combinedCalculators.AddRange(calculator.AggregatedValuesCalculators));

            return(!calculators.All(c => c._canBeAggregated)
                    ? new CellValueCalculator(
                       resultType,
                       calculateExpression,
                       allAggregatedValueCalculators)
                    : new CellValueCalculator(
                       resultType,
                       GetPrecalculatedAggregatedValue(resultType, parameters, aggregatedValueKey),
                       allAggregatedValueCalculators.Insert(0, KeyValuePair.Create(aggregatedValueKey, calculateExpression)),
                       canBeAggregated: true));
        }
 public FormulaExpressionsCompiler(FormulaParameters formulaParameters)
 {
     Parameters = formulaParameters;
 }