Example #1
0
        // https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/functions/function-left-mid-right
        public static FormulaValue Mid(IRContext irContext, FormulaValue[] args)
        {
            var         errors = new List <ErrorValue>();
            NumberValue start  = (NumberValue)args[1];

            if (double.IsNaN(start.Value) || double.IsInfinity(start.Value) || start.Value <= 0)
            {
                errors.Add(CommonErrors.ArgumentOutOfRange(start.IRContext));
            }

            NumberValue count = (NumberValue)args[2];

            if (double.IsNaN(count.Value) || double.IsInfinity(count.Value) || count.Value < 0)
            {
                errors.Add(CommonErrors.ArgumentOutOfRange(count.IRContext));
            }

            if (errors.Count != 0)
            {
                return(ErrorValue.Combine(irContext, errors));
            }

            StringValue source      = (StringValue)args[0];
            var         start0Based = (int)(start.Value - 1);

            if (source.Value == "" || start0Based >= source.Value.Length)
            {
                return(new StringValue(irContext, ""));
            }

            var minCount = Math.Min((int)count.Value, source.Value.Length - start0Based);
            var result   = source.Value.Substring(start0Based, minCount);

            return(new StringValue(irContext, result));
        }
Example #2
0
        public static FormulaValue SortTable(EvalVisitor runner, SymbolContext symbolContext, IRContext irContext, FormulaValue[] args)
        {
            var arg0 = (TableValue)args[0];
            var arg1 = (LambdaFormulaValue)args[1];
            var arg2 = (StringValue)args[2];

            var pairs = arg0.Rows.Select(row =>
            {
                if (row.IsValue)
                {
                    var childContext = symbolContext.WithScopeValues(row.Value);
                    return(new KeyValuePair <DValue <RecordValue>, FormulaValue>(row, arg1.Eval(runner, childContext)));
                }
                return(new KeyValuePair <DValue <RecordValue>, FormulaValue>(row, row.ToFormulaValue()));
            }).ToList();

            var errors = new List <ErrorValue>(pairs.Select(pair => pair.Value).OfType <ErrorValue>());

            var allNumbers  = pairs.All(pair => IsValueTypeErrorOrBlank <NumberValue>(pair.Value));
            var allStrings  = pairs.All(pair => IsValueTypeErrorOrBlank <StringValue>(pair.Value));
            var allBooleans = pairs.All(pair => IsValueTypeErrorOrBlank <BooleanValue>(pair.Value));

            if (!(allNumbers || allStrings || allBooleans))
            {
                errors.Add(CommonErrors.RuntimeTypeMismatch(irContext));
                return(ErrorValue.Combine(irContext, errors));
            }

            if (errors.Count != 0)
            {
                return(ErrorValue.Combine(irContext, errors));
            }

            var compareToResultModifier = 1;

            if (arg2.Value.ToLower() == "descending")
            {
                compareToResultModifier = -1;
            }

            if (allNumbers)
            {
                return(SortValueType <NumberValue, double>(pairs, irContext, compareToResultModifier));
            }
            else if (allStrings)
            {
                return(SortValueType <StringValue, string>(pairs, irContext, compareToResultModifier));
            }
            else
            {
                return(SortValueType <BooleanValue, bool>(pairs, irContext, compareToResultModifier));
            }
        }
Example #3
0
        public static FormulaValue CountIf(EvalVisitor runner, SymbolContext symbolContext, IRContext irContext, FormulaValue[] args)
        {
            // Streaming
            var sources = (TableValue)args[0];
            var filter  = (LambdaFormulaValue)args[1];

            int count = 0;

            var errors = new List <ErrorValue>();

            foreach (var row in sources.Rows)
            {
                if (row.IsValue)
                {
                    var childContext = symbolContext.WithScopeValues(row.Value);
                    var result       = filter.Eval(runner, childContext);

                    if (result is ErrorValue error)
                    {
                        errors.Add(error);
                        continue;
                    }

                    bool include = ((BooleanValue)result).Value;

                    if (include)
                    {
                        count++;
                    }
                }
                if (row.IsError)
                {
                    errors.Add(row.Error);
                }
            }

            if (errors.Count != 0)
            {
                return(ErrorValue.Combine(irContext, errors));
            }

            return(new NumberValue(irContext, count));
        }
Example #4
0
        // https://docs.microsoft.com/en-us/powerapps/maker/canvas-apps/functions/function-isblank-isempty
        // Take first non-blank value.
        //
        public static FormulaValue Coalesce(EvalVisitor runner, SymbolContext symbolContext, IRContext irContext, FormulaValue[] args)
        {
            var errors = new List <ErrorValue>();

            foreach (var arg in args)
            {
                var res = runner.EvalArg <ValidFormulaValue>(arg, symbolContext, arg.IRContext);

                if (res.IsValue)
                {
                    var val = res.Value;
                    if (!(val is StringValue str && str.Value == ""))
                    {
                        if (errors.Count == 0)
                        {
                            return(res.ToFormulaValue());
                        }
                        else
                        {
                            return(ErrorValue.Combine(irContext, errors));
                        }
                    }
                }
                if (res.IsError)
                {
                    errors.Add(res.Error);
                }
            }
            if (errors.Count == 0)
            {
                return(new BlankValue(irContext));
            }
            else
            {
                return(ErrorValue.Combine(irContext, errors));
            }
        }
Example #5
0
        /// <summary>
        /// A pipeline that maps blanks to a value, checks
        /// runtime types, and possibly map values to errors
        /// before filtering errors and possibly returning
        /// an ErrorValue instead of executing
        /// </summary>
        /// <typeparam name="T">The specific FormulaValue type that the implementation of the builtin expects, for exmaple NumberValue for math functions</typeparam>
        /// <param name="expandArguments">This stage of the pipeline can be used to expand an argument list if some of the arguments are optional and missing</param>
        /// <param name="replaceBlankValues">This stage can be used to transform Blank() into something else, for example the number 0</param>
        /// <param name="checkRuntimeTypes">This stage can be used to check to that all the arguments have type T, or check that all arguments have type T | Blank(), etc.</param>
        /// <param name="checkRuntimeValues">This stage can be used to generate errors if specific values occur in the arguments, for example infinity, NaN, etc.</param>
        /// <param name="returnBehavior">A flag that can be used to activate pre-defined early return behavior, such as returning Blank() if any argument is Blank()</param>
        /// <param name="targetFunction">The implementation of the builtin function</param>
        /// <returns></returns>
        private static FunctionPtr StandardErrorHandling <T>(
            Func <IRContext, IEnumerable <FormulaValue>, IEnumerable <FormulaValue> > expandArguments,
            Func <IRContext, int, FormulaValue> replaceBlankValues,
            Func <IRContext, int, FormulaValue, FormulaValue> checkRuntimeTypes,
            Func <IRContext, int, FormulaValue, FormulaValue> checkRuntimeValues,
            ReturnBehavior returnBehavior,
            Func <EvalVisitor, SymbolContext, IRContext, T[], FormulaValue> targetFunction
            ) where T : FormulaValue
        {
            return((runner, symbolContext, irContext, args) =>
            {
                var argumentsExpanded = expandArguments(irContext, args);

                var blankValuesReplaced = argumentsExpanded.Select((arg, i) =>
                {
                    if (arg is BlankValue)
                    {
                        return replaceBlankValues(arg.IRContext, i);
                    }
                    else
                    {
                        return arg;
                    }
                });

                var runtimeTypesChecked = blankValuesReplaced.Select((arg, i) => checkRuntimeTypes(irContext, i, arg));

                var runtimeValuesChecked = runtimeTypesChecked.Select((arg, i) =>
                {
                    if (arg is T t)
                    {
                        return checkRuntimeValues(arg.IRContext, i, t);
                    }
                    else
                    {
                        return arg;
                    }
                });

                var errors = runtimeValuesChecked.OfType <ErrorValue>();
                if (errors.Count() != 0)
                {
                    return ErrorValue.Combine(irContext, errors);
                }

                switch (returnBehavior)
                {
                case ReturnBehavior.ReturnBlankIfAnyArgIsBlank:
                    if (runtimeValuesChecked.Any(arg => arg is BlankValue))
                    {
                        return new BlankValue(IRContext.NotInSource(FormulaType.Blank));
                    }
                    break;

                case ReturnBehavior.ReturnEmptyStringIfAnyArgIsBlank:
                    if (runtimeValuesChecked.Any(arg => arg is BlankValue))
                    {
                        return new StringValue(IRContext.NotInSource(FormulaType.String), "");
                    }
                    break;

                case ReturnBehavior.ReturnFalseIfAnyArgIsBlank:
                    if (runtimeValuesChecked.Any(arg => arg is BlankValue))
                    {
                        return new BooleanValue(IRContext.NotInSource(FormulaType.Boolean), false);
                    }
                    break;

                case ReturnBehavior.AlwaysEvaluateAndReturnResult:
                    break;
                }

                return targetFunction(runner, symbolContext, irContext, runtimeValuesChecked.Select(arg => arg as T).ToArray());
            });
        }