// 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)); }
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)); } }
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)); }
// 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)); } }
/// <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()); }); }