Skip to content

Commit

Permalink
♻️ Code refactoring and some fixes
Browse files Browse the repository at this point in the history
- Adjustments to some files and folders
- Addition of missing assert in the boolean test evaluator
- Fixed index out of range exception in the FindNumber method in ContentBuilder
- Updated the Microsoft.Extensions.ObjectPools package (7.0.1 to 8.0.3)
  • Loading branch information
leonardo-tx committed Mar 18, 2024
1 parent 5d4303f commit 049d60e
Show file tree
Hide file tree
Showing 14 changed files with 189 additions and 107 deletions.
22 changes: 10 additions & 12 deletions src/Byces.Calculator/Builders/BuiltExpressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ internal sealed class BuiltExpressions

public BuiltExpressions(CalculatorOptions options, Assembly[] assemblies)
{
_conflictsItems = new object[] { BeforeConflictItems, AfterConflictItems };

InstantiateExpressionItemsInAssembly(options, FunctionType.Assembly);
foreach (Assembly assembly in assemblies)
{
InstantiateExpressionItemsInAssembly(CalculatorOptions.Default, assembly);
}
foreach (BeforeVariableItem item in BeforeConflictItems.UniqueItems)
{
item.StringRepresentations = Array.Empty<string>();
}
foreach (OperatorItem item in AfterConflictItems.UniqueItems)
{
item.StringRepresentations = Array.Empty<string>();
}
}

private void InstantiateExpressionItemsInAssembly(CalculatorOptions options, Assembly assembly)
Expand All @@ -32,27 +38,19 @@ private void InstantiateExpressionItemsInAssembly(CalculatorOptions options, Ass
(options & CalculatorOptions.RemoveDefaultVariables) == 0 && type.IsSubclassOf(VariableType))
{
BeforeVariableItem instance = (BeforeVariableItem)Activator.CreateInstance(type)!;
AddRepresentation(instance, 0);
BeforeConflictItems.AddItem(instance);

continue;
}
if (type.IsSubclassOf(OperatorType))
{
OperatorItem instance = (OperatorItem)Activator.CreateInstance(type)!;
AddRepresentation(instance, 1);
AfterConflictItems.AddItem(instance);
}
}
}

private void AddRepresentation<T>(T instance, int conflictIndex) where T : ExpressionItem<T>
{
ConflictItems<T> conflictItems = (ConflictItems<T>)_conflictsItems[conflictIndex];
conflictItems.AddItem(instance);
}

internal readonly ConflictItems<BeforeVariableItem> BeforeConflictItems = new();
internal readonly ConflictItems<OperatorItem> AfterConflictItems = new();

private readonly object[] _conflictsItems;
}
}
91 changes: 24 additions & 67 deletions src/Byces.Calculator/Builders/ConflictItems.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,50 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Byces.Calculator.Collections;
using Byces.Calculator.Expressions.Items;

namespace Byces.Calculator.Builders
{
internal sealed class ConflictItems<T> where T : ExpressionItem<T>
{
private readonly HashSet<T> _uniqueItems = new();

private int _count;

private KeyValuePair<string, T>?[] _items = new KeyValuePair<string, T>?[1];
internal readonly HashSet<T> UniqueItems = new();

private readonly CharSpanDictionary<T> _itemsSpan = new();
private readonly Dictionary<char, T> _itemsChar = new();

internal void AddItem(T item)
{
if (!_uniqueItems.Add(item)) return;

int requiredLength = _count + item.StringRepresentations.Length;
IncreaseInternalArraySize(requiredLength);
AddCollisions(item);
if (!UniqueItems.Add(item)) return;

AddCollisions(item);
foreach (string representation in item.StringRepresentations)
{
AddPair(representation, item);
}
}

private void AddPair(string representation, T item)
{
int index = GetIndex(representation);
while (_items[index] != null)
{
if (representation.Equals(_items[index]!.Value.Key, StringComparison.OrdinalIgnoreCase))
if (representation.Length > 1)
{
if (_itemsSpan.Add(representation, item)) continue;
throw new ArgumentException($"Unable to initialize the type. The {item.GetType().FullName} class has a string representation that collides with another.");
}
char lowerVariant = char.ToLower(representation[0]);
char upperVariant = char.ToUpper(representation[0]);

index = (index + 1) % _items.Length;
if (lowerVariant != upperVariant)
{
if (!_itemsChar.TryAdd(lowerVariant, item))
throw new ArgumentException($"Unable to initialize the type. The {item.GetType().FullName} class has a string representation that collides with another.");
}
if (!_itemsChar.TryAdd(upperVariant, item))
throw new ArgumentException($"Unable to initialize the type. The {item.GetType().FullName} class has a string representation that collides with another.");
}
_items[index] = new KeyValuePair<string, T>(representation, item);
_count += 1;
}

private void AddCollisions(T item)
{
int indexForSameInstance = 1;
foreach (string stringRepresentation in item.StringRepresentations)
{
ReadOnlySpan<char> spanRepresentation = stringRepresentation;
foreach (T anotherItem in _uniqueItems)
foreach (T anotherItem in UniqueItems)
{
bool sameInstance = item == anotherItem;
for (int i = sameInstance ? indexForSameInstance : 0; i < anotherItem.StringRepresentations.Length; i++)
Expand Down Expand Up @@ -83,52 +80,12 @@ private void AddCollisions(T item)

internal T Parse(ReadOnlySpan<char> span)
{
if (TryParse(span, out T? type)) return type;
throw new ArgumentException();
return span.Length == 1 ? _itemsChar[span[0]] : _itemsSpan[span];
}

internal bool TryParse(ReadOnlySpan<char> span, [NotNullWhen(true)] out T? type)
{
int index = GetIndex(span);
while (_items[index] != null)
{
if (span.Equals(_items[index]!.Value.Key, StringComparison.OrdinalIgnoreCase))
{
type = _items[index]!.Value.Value;
return true;
}
index = (index + 1) % _items.Length;
}
type = null;
return false;
}

private void IncreaseInternalArraySize(int requiredLength)
{
int itemsLength = _items.Length;
while (requiredLength > itemsLength * 0.75) itemsLength *= 2;

if (itemsLength == _items.Length) return;

KeyValuePair<string, T>?[] oldItems = _items;
_items = new KeyValuePair<string, T>?[itemsLength];

foreach (KeyValuePair<string, T>? oldItem in oldItems)
{
if (oldItem == null) continue;

int index = GetIndex(oldItem.Value.Key);
while (_items[index] != null)
{
index = (index + 1) % _items.Length;
}
_items[index] = oldItem;
}
}

private int GetIndex(ReadOnlySpan<char> s)
{
return Math.Abs(string.GetHashCode(s, StringComparison.OrdinalIgnoreCase) % _items.Length);
return span.Length == 1 ? _itemsChar.TryGetValue(span[0], out type) : _itemsSpan.TryGetValue(span, out type);
}
}
}
25 changes: 22 additions & 3 deletions src/Byces.Calculator/Builders/ContentBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Threading;
using Byces.Calculator.Collections;
using Byces.Calculator.Enums;
using Byces.Calculator.Expressions;
using Byces.Calculator.Expressions.Items;
Expand Down Expand Up @@ -133,15 +134,33 @@ private bool ScanExpressionAfterVariable(ReadOnlySpan<char> expressionSpan)
private bool FindNumber(ReadOnlySpan<char> expressionSpan)
{
_isNegative = expressionSpan[_lastIndex] == '-';
if (_isNegative || expressionSpan[_lastIndex] == '+') { _lastIndex++; _firstIndex++; }
if (char.IsLetter(expressionSpan[_lastIndex])) return false;
if (_isNegative || expressionSpan[_lastIndex] == '+')
{
_lastIndex++;
if (_lastIndex == expressionSpan.Length) return false;
_firstIndex++;
}

var numberStyles = NumberStyles.None;
if (char.IsDigit(expressionSpan[_lastIndex]))
{
_lastIndex++;
}
else if (expressionSpan[_lastIndex] == _decimalSeparator)
{
numberStyles |= NumberStyles.AllowDecimalPoint;
_lastIndex++;
}
else
{
return false;
}

for (bool hasSignal = false; _lastIndex < expressionSpan.Length; _lastIndex++)
{
char currentChar = expressionSpan[_lastIndex];
if (char.IsDigit(currentChar)) continue;
if ((numberStyles & NumberStyles.AllowDecimalPoint) == NumberStyles.None)
if ((numberStyles & NumberStyles.AllowDecimalPoint) == 0)
{
if (currentChar == _decimalSeparator) { numberStyles |= NumberStyles.AllowDecimalPoint; continue; }
if (currentChar == _groupSeparator) { numberStyles |= NumberStyles.AllowThousands; continue; }
Expand Down
9 changes: 4 additions & 5 deletions src/Byces.Calculator/Builders/ResultBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Byces.Calculator.Cache;
using Byces.Calculator.Enums;
using Byces.Calculator.Collections;
using Byces.Calculator.Expressions;

namespace Byces.Calculator.Builders
Expand All @@ -14,7 +13,7 @@ public ResultBuilder(CalculatorDependencies dependencies)
_dependencies = dependencies;
_content = new Content();
_contentBuilder = new ContentBuilder(_content, dependencies);
_expressionBuilder = new List<char>();
_expressionBuilder = dependencies.HasWhitespaceCheck() ? new List<char>() : null!;
}

private readonly CalculatorDependencies _dependencies;
Expand All @@ -35,7 +34,7 @@ public void Build(ReadOnlySpan<char> expressionSpan)

private ReadOnlySpan<char> GetFormattedExpression(ReadOnlySpan<char> expressionSpan)
{
if ((_dependencies.Options & CalculatorOptions.RemoveWhitespaceChecker) != 0) return expressionSpan;
if (!_dependencies.HasWhitespaceCheck()) return expressionSpan;
for (int i = 0; i < expressionSpan.Length; i++)
{
if (char.IsWhiteSpace(expressionSpan[i])) continue;
Expand All @@ -52,7 +51,7 @@ private void BuildContent(ReadOnlySpan<char> expressionSpan)
_content.Process();
return;
}
if (_dependencies.CachedExpressions.TryGetContent(expressionSpan, out CachedContent? cachedContent))
if (_dependencies.CachedExpressions.TryGetValue(expressionSpan, out CachedContent? cachedContent))
{
cachedContent.CopyTo(_content);
_content.Process();
Expand Down
2 changes: 1 addition & 1 deletion src/Byces.Calculator/Byces.Calculator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.11" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Byces.Calculator.Cache.Variables;
using Byces.Calculator.Expressions;

namespace Byces.Calculator.Cache
namespace Byces.Calculator.Collections
{
internal sealed class CachedContent
{
Expand Down
103 changes: 103 additions & 0 deletions src/Byces.Calculator/Collections/CharSpanDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

namespace Byces.Calculator.Collections
{
internal sealed class CharSpanDictionary<TValue>
{
private const double LoadFactor = 0.75;
private const int InitialCapacity = 16;

public CharSpanDictionary()
{
_items = new IList<KeyValuePair<string, TValue>>[InitialCapacity];
Array.Fill(_items, Array.Empty<KeyValuePair<string, TValue>>());
}

private IList<KeyValuePair<string, TValue>>[] _items;
private int _count;

public bool Add(string key, TValue value)
{
EnsureCapacity();
int index = GetIndex(key);

if (_items[index].Count == 0)
{
_items[index] = new List<KeyValuePair<string, TValue>> { new(key, value) };
}
else
{
if (_items[index].Any(pair => key.Equals(pair.Key, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
_items[index].Add(new KeyValuePair<string, TValue>(key, value));
}
_count += 1;
return true;
}

public TValue this[ReadOnlySpan<char> s]
{
get
{
if (TryGetValue(s, out TValue? value)) return value;
throw new ArgumentException("The specified key does not exist in the collection");
}
}

public bool TryGetValue(ReadOnlySpan<char> s, [MaybeNullWhen(false)] out TValue value)
{
int index = GetIndex(s);
if (_items[index].Count == 0)
{
value = default;
return false;
}
IList<KeyValuePair<string, TValue>> pairs = _items[index];
for (int i = 0; i < pairs.Count; i++)
{
KeyValuePair<string, TValue> pair = pairs[i];
if (s.Equals(pair.Key, StringComparison.OrdinalIgnoreCase))
{
value = pair.Value;
return true;
}
}
value = default;
return false;
}

private void EnsureCapacity()
{
if (_count + 1 <= _items.Length * LoadFactor) return;

IList<KeyValuePair<string, TValue>>[] oldItems = _items;
_items = new IList<KeyValuePair<string, TValue>>[_items.Length * 2];
Array.Fill(_items, Array.Empty<KeyValuePair<string, TValue>>());

foreach (IList<KeyValuePair<string, TValue>> oldItem in oldItems)
{
foreach (KeyValuePair<string, TValue> pair in oldItem)
{
int index = GetIndex(pair.Key);
if (_items[index].Count == 0)
{
_items[index] = new List<KeyValuePair<string, TValue>> { pair };
continue;
}
_items[index].Add(pair);
}
}
}

private int GetIndex(ReadOnlySpan<char> s)
{
int hashCode = string.GetHashCode(s, StringComparison.OrdinalIgnoreCase);
return Math.Abs(hashCode % _items.Length);
}
}
}
Loading

0 comments on commit 049d60e

Please sign in to comment.