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; }