Skip to content

Commit

Permalink
Don't pass empty numbers/logical values into number/logical sequence
Browse files Browse the repository at this point in the history
  • Loading branch information
anmcgrath committed Mar 6, 2024
1 parent e98d59e commit cbd3832
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 79 deletions.
6 changes: 6 additions & 0 deletions src/BlazorDatasheet.Core/FormulaEngine/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public void RegisterFunction(string name, ISheetFunction value)
_functions[name.ToLower()] = value;
}

public IEnumerable<CellValue> GetNonEmptyInRange(Reference reference)
{
return _sheet.Cells.GetNonEmptyCellValues(reference.ToRegion())
.Select(x => x.value).ToArray();
}

public CellValue GetCellValue(int row, int col) => _sheet.Cells.GetCellValue(row, col);

public CellValue[][] GetRangeValues(Reference reference)
Expand Down
1 change: 1 addition & 0 deletions src/BlazorDatasheet.Formula.Core/IEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ public interface IEnvironment
CellValue GetVariable(string variableIdentifier);
void SetVariable(string name, CellValue value);
void RegisterFunction(string name, ISheetFunction value);
public IEnumerable<CellValue> GetNonEmptyInRange(Reference reference);
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private CellValue EvaluateFunctionCall(FunctionExpression node)
if (arg.IsError() && !func.AcceptsErrors)
return arg;

convertedArgs[argIndex] = _parameterConverter.ConvertVal(arg, paramDefinition);
convertedArgs[argIndex] = _parameterConverter.ConvertVal(arg, paramDefinition.Type);

if (IsConsumable(paramDefinition))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,126 +23,108 @@ public ParameterConverter(IEnvironment environment, CellValueCoercer cellValueCo
/// <param name="value"></param>
/// <param name="definition"></param>
/// <returns></returns>
public CellValue ConvertVal(CellValue value, ParameterDefinition definition)
public CellValue ConvertVal(CellValue value, ParameterType type)
{
if (value.IsCellReference() && definition.Type.IsScalar())
if (value.IsCellReference() && type.IsScalar())
value = GetCellReferenceValue(value);

if (value.IsError())
return value;

switch (definition.Type)
switch (type)
{
case ParameterType.Any:
return value;
case ParameterType.Number:
return ToNumber(value, definition);
return ToNumber(value);
case ParameterType.NumberSequence:
return ToNumberSequence(value, definition);
return ToNumberSequence(value);
case ParameterType.Logical:
return ToLogical(value, definition);
return ToLogical(value);
case ParameterType.LogicalSequence:
return ToLogicalSequence(value, definition);
return ToLogicalSequence(value);
case ParameterType.Text:
return ToText(value, definition);
return ToText(value);
case ParameterType.Date:
return ToDate(value, definition);
return ToDate(value);
case ParameterType.Array:
return ToArray(value, definition);
return ToArray(value);
case ParameterType.Integer:
return ToInteger(value, definition);
return ToInteger(value);
default:
return CellValue.Error(ErrorType.Value);
}
}

private CellValue ToInteger(CellValue value, ParameterDefinition definition)
private CellValue ToInteger(CellValue value)
{
var asNumber = ToNumber(value, definition);
var asNumber = ToNumber(value);
if (asNumber.IsError())
return asNumber;

return CellValue.Number(Convert.ToInt32(asNumber.Data!));
}

private CellValue ToLogicalSequence(CellValue value, ParameterDefinition definition)
private CellValue ToLogicalSequence(CellValue value)
{
CellValue[]? values = null;

if (value.ValueType == CellValueType.Reference)
{
List<CellValue> results = new List<CellValue>();
var range = _environment.GetRangeValues(value.GetValue<Reference>()!);
for (int i = 0; i < range.Length; i++)
{
for (int j = 0; j < range[i].Length; j++)
{
if (range[i][j].ValueType == CellValueType.Logical ||
range[i][j].ValueType == CellValueType.Error)
{
results.Add(range[i][j]);
}

if (range[i][j].ValueType == CellValueType.Number)
results.Add(ToLogical(range[i][j], definition));
}
}
values = _environment.GetNonEmptyInRange((Reference)value.Data!).ToArray();
else if (value.ValueType == CellValueType.Array)
values = value.GetValue<CellValue[][]>()!.SelectMany(x => x).ToArray();

return CellValue.Sequence(results.ToArray());
}
if (values == null) return CellValue.Sequence(new[] { ToLogical(value) });

if (value.ValueType == CellValueType.Array)
var results = new List<CellValue>();
foreach (var val in values)
{
var array = value.GetValue<CellValue[][]>()!;
var cellValueLogOrError = array.SelectMany(x => x)
.Where(y => y.ValueType == CellValueType.Logical || y.ValueType == CellValueType.Error);
return CellValue.Sequence(cellValueLogOrError.ToArray());
if (val.IsEmpty)
continue;
if (val.ValueType == CellValueType.Logical || val.ValueType == CellValueType.Error)
results.Add(val);
else if (val.ValueType == CellValueType.Number)
results.Add(ToLogical(val));
}

return CellValue.Sequence(new[] { ToLogical(value, definition) });
return CellValue.Sequence(results.ToArray());
}

private CellValue ToNumberSequence(CellValue value, ParameterDefinition definition)
private bool IsLogicalOrError(CellValue value)
{
if (value.ValueType == CellValueType.Reference)
{
List<CellValue> results = new List<CellValue>();
var valAsREf = (Reference)value.Data!;
var range = _environment.GetRangeValues(valAsREf);
for (int i = 0; i < range.Length; i++)
{
for (int j = 0; j < range[i].Length; j++)
{
if (range[i][j].ValueType == CellValueType.Number ||
range[i][j].ValueType == CellValueType.Error)
{
results.Add(range[i][j]);
}
}
}
return value.ValueType == CellValueType.Logical ||
value.ValueType == CellValueType.Error;
}

return CellValue.Sequence(results.ToArray());
}
private CellValue ToNumberSequence(CellValue value)
{
CellValue[]? values = null;

if (value.ValueType == CellValueType.Array)
if (value.ValueType == CellValueType.Reference)
values = _environment.GetNonEmptyInRange((Reference)value.Data!).ToArray();
else if (value.ValueType == CellValueType.Array)
values = value.GetValue<CellValue[][]>()!.SelectMany(x => x).ToArray();

if (values == null) return CellValue.Sequence(new[]
{
var array = value.GetValue<CellValue[][]>()!;
var vals = new List<CellValue>();
foreach (var row in array)
{
foreach (var col in row)
{
if (col.ValueType == CellValueType.Number || col.IsError())
vals.Add(col);
}
}
ToNumber(value)
});

return CellValue.Sequence(vals.ToArray());
var results = new List<CellValue>();
foreach (var val in values)
{
if (val.IsEmpty)
continue;
if (val.ValueType == CellValueType.Error)
results.Add(val);
else if (val.ValueType == CellValueType.Number)
results.Add(ToNumber(val));
}

// single value
return CellValue.Sequence(new[] { ToNumber(value, definition) });
return CellValue.Sequence(results.ToArray());
}

private CellValue ToArray(CellValue value, ParameterDefinition definition)
private CellValue ToArray(CellValue value)
{
if (value.IsEmpty)
return CellValue.Array(Array.Empty<CellValue[]>());
Expand All @@ -168,31 +150,31 @@ private CellValue ToArray(CellValue value, ParameterDefinition definition)
return CellValue.Array(new[] { new[] { value } });
}

private CellValue ToDate(CellValue value, ParameterDefinition definition)
private CellValue ToDate(CellValue value)
{
var coerceDate = _cellValueCoercer.TryCoerceDate(value, out var val);
if (coerceDate)
return CellValue.Date(val);
return CellValue.Error(ErrorType.Value);
}

private CellValue ToNumber(CellValue value, ParameterDefinition definition)
private CellValue ToNumber(CellValue value)
{
var coerceNum = _cellValueCoercer.TryCoerceNumber(value, out var val);
if (coerceNum)
return CellValue.Number(val);
return CellValue.Error(ErrorType.Value);
}

private CellValue ToLogical(CellValue value, ParameterDefinition definition)
private CellValue ToLogical(CellValue value)
{
var corceBool = _cellValueCoercer.TryCoerceBool(value, out var val);
if (corceBool)
return CellValue.Logical(val);
return CellValue.Error(ErrorType.Value);
}

private CellValue ToText(CellValue value, ParameterDefinition definition)
private CellValue ToText(CellValue value)
{
var coerceText = _cellValueCoercer.TryCoerceString(value, out var val);
if (coerceText)
Expand Down
31 changes: 31 additions & 0 deletions test/BlazorDatasheet.Test/Formula/ParameterConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Linq;
using BlazorDatasheet.Formula.Core;
using BlazorDatasheet.Formula.Core.Interpreter.Evaluation;
using FluentAssertions;
using NUnit.Framework;

namespace BlazorDatasheet.Test.Formula;

public class ParameterConverterTests
{
[Test]
public void Array_Conversion_To_NumberSequence()
{
var arr = CellValue.Array(new[] { new[] { CellValue.Number(2), CellValue.Logical(true) } });
var env = new TestEnvironment();
var converter = new ParameterConverter(env, new CellValueCoercer(env));
var ns = converter.ConvertVal(arr, ParameterType.NumberSequence);
ns
.GetValue<CellValue[]>()!
.Select(x => x.Data)
.Should()
.BeEquivalentTo(new[] { 2.0 });

var ls = converter.ConvertVal(arr, ParameterType.LogicalSequence);
ls
.GetValue<CellValue[]>()!
.Select(x => x.Data)
.Should()
.BeEquivalentTo(new[] { true, true });
}
}
7 changes: 7 additions & 0 deletions test/BlazorDatasheet.Test/Formula/TestEnvironment.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BlazorDatasheet.DataStructures.Geometry;
using BlazorDatasheet.DataStructures.References;
using BlazorDatasheet.DataStructures.Util;
Expand Down Expand Up @@ -35,6 +36,12 @@ public void RegisterFunction(string name, ISheetFunction functionDefinition)
_functions[name] = functionDefinition;
}

public IEnumerable<CellValue> GetNonEmptyInRange(Reference reference)
{
return GetRangeValues(reference)
.SelectMany(x => x);
}

public void SetVariable(string name, object variable)
{
SetVariable(name, new CellValue(variable));
Expand Down

0 comments on commit cbd3832

Please sign in to comment.