Skip to content

Commit

Permalink
Check all symbols are unique & migrate to .NET 7
Browse files Browse the repository at this point in the history
The former checks ensured no duplicate symbols within each category, but did not cross check them with other categories.
  • Loading branch information
BLM16 committed Dec 30, 2022
1 parent 5e5a867 commit 026bd4c
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 154 deletions.
2 changes: 1 addition & 1 deletion .github/actions/build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ runs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
dotnet-version: 7.0.x

- name: Restore dependencies
run: dotnet restore
Expand Down
4 changes: 2 additions & 2 deletions Calculator.Examples/Calculator.Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>Examples</RootNamespace>

<Authors>Bradley Myers</Authors>
<Company>BLM16</Company>
<Copyright>Copyright © 2021 Bradley Myers. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/BLM16/Tokenized-Calculator</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>2.1.0</Version>
<Version>2.1.1</Version>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageTags>calculator</PackageTags>
<PackageIcon>icon.png</PackageIcon>
Expand Down
7 changes: 4 additions & 3 deletions Calculator.Examples/ConsoleIO.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
using BLM16.Util.Calculator;
using BLM16.Util.Calculator.Models;

var modulusOperator = new Operator('%', 20, (a, b) => a % b);

var calculator = new Calculator(new[] { modulusOperator });
var calculator = new Calculator
{
Operators = new[] { DefaultOperators.Modulus }
};

while (true)
{
Expand Down
2 changes: 1 addition & 1 deletion Calculator.Tests/Calculator.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<IsPackable>false</IsPackable>

<Authors>Bradley Myers</Authors>
Expand Down
6 changes: 3 additions & 3 deletions Calculator.Tests/LexerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ private static IEnumerable<object[]> NegativeExpressions
new Token(TokenType.RBRACK, ')'),
}
};

yield return new object[]
{
"2-(17-5)",
Expand Down Expand Up @@ -177,6 +177,7 @@ private static IEnumerable<object[]> NegativeExpressions
new Token(TokenType.NUMBER, '0'),
new Token(TokenType.OPERATOR, DefaultOperators.Subtraction),
new Token(TokenType.NUMBER, '6'),
new Token(TokenType.RBRACK, ')'),
new Token(TokenType.OPERATOR, DefaultOperators.Multiplication),
new Token(TokenType.LBRACK, '('),
new Token(TokenType.LBRACK, '('),
Expand All @@ -188,8 +189,7 @@ private static IEnumerable<object[]> NegativeExpressions
new Token(TokenType.NUMBER, '0'),
new Token(TokenType.OPERATOR, DefaultOperators.Subtraction),
new Token(TokenType.NUMBER, '5'),
new Token(TokenType.RBRACK, ')'),
new Token(TokenType.RBRACK, ')'),
new Token(TokenType.RBRACK, ')'),
new Token(TokenType.RBRACK, ')'),
new Token(TokenType.RBRACK, ')')
}
Expand Down
124 changes: 83 additions & 41 deletions Calculator/Calculator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using BLM16.Util.Calculator.Models;
using System;
using System.Collections.Generic;
using System.Linq;

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Calculator.Tests")]
Expand All @@ -11,55 +12,77 @@ namespace BLM16.Util.Calculator;
public class Calculator
{
/// <summary>
/// The Standardizer for standardizing the equation
/// Contains a list of all the symbols used
/// </summary>
private readonly Standardizer standardizer;
/// <summary>
/// The Lexer for tokenizing the equation
/// </summary>
private readonly Lexer lexer;
/// <summary>
/// The Parser for evaluating the equation
/// </summary>
private readonly Parser parser;
private readonly List<string> _symbols = new();

private Operator[] _operators = Array.Empty<Operator>();
private Constant[] _constants = DefaultConstantList;
private Function[] _functions = DefaultFunctionList;

/// <summary>
/// The operators the calculator can use
/// The operators the calculator can use.
/// Always includes <see cref="BuiltinOperatorList"/>.
/// </summary>
private readonly Operator[] operators;
/// <exception cref="ArgumentException">Thrown when duplicate operators are encountered.</exception>
public Operator[] Operators
{
get => _operators;
init
{
var symbols = value.Select(o => o.Symbol.ToString());

CheckDuplicateSymbolsAndThrow(symbols);

_symbols.AddRange(symbols);
_operators = _operators.Concat(value).ToArray();
}
}

/// <summary>
/// The constants the calculator can use
/// </summary>
private readonly Constant[] constants;
/// <exception cref="ArgumentException">Thrown when duplicate constants are encountered.</exception>
public Constant[] Constants
{
get => _constants;
init
{
var symbols = value.SelectMany(c => c.Symbols);

CheckDuplicateSymbolsAndThrow(symbols);

_symbols.AddRange(symbols);
_constants = value;
}
}

/// <summary>
/// The functions the calculator can use
/// </summary>
private readonly Function[] functions;
/// <exception cref="ArgumentException">Thrown when duplicate functions are encountered.</exception>
public Function[] Functions
{
get => _functions;
init
{
var symbols = value.SelectMany(f => f.Symbols);

/// <param name="operators">The list of operators the calculator recognizes. Always includes <see cref="BuiltinOperatorList"/>.</param>
/// <param name="constants">The list of constants the calculator recognizes. Defaults to <see cref="DefaultConstantList"/> if no value is provided.</param>
/// <param name="functions">The list of functions the calculator recognizes. Defaults to <see cref="DefaultFunctionList"/> if no value is provided.</param>
public Calculator(Operator[] operators = null, Constant[] constants = null, Function[] functions = null)
CheckDuplicateSymbolsAndThrow(symbols);

_symbols.AddRange(symbols);
_functions = value;
}
}

public Calculator()
{
// Verify operators are unique
this.operators = new UniqueList<Operator>((a, b) => a.Symbol.Equals(b.Symbol))
.With(BuiltinOperatorList) // Add default operators
.With(operators ?? Array.Empty<Operator>()) // Add provided operators if there are any
.ToArray();

// Verify constants are unique
this.constants = new UniqueList<Constant>((a, b) => a.Symbols.Intersect(b.Symbols).Any())
.With(constants ?? DefaultConstantList) // Use the default constants if no constants are provided
.ToArray();

// Verify functions are unique
this.functions = new UniqueList<Function>((a, b) => a.Symbols.Intersect(b.Symbols).Any())
.With(functions ?? DefaultFunctionList) // Use the default functions if no functions are provided
.ToArray();

standardizer = new Standardizer(this.operators, this.constants, this.functions);
lexer = new Lexer(this.operators);
parser = new Parser();
// Initialize Operators here instead of the backing field so the custom
// init logic adds the operator symbols to _symbols.
// Functions and Constants are overwritten instead of preserving builtins
// therefore their symbols should not be added to _symbols and the init
// logic does not need to run. This stops builtin operator duplication.
Operators = BuiltinOperatorList;
}

/// <summary>
Expand All @@ -72,13 +95,32 @@ public double Calculate(string equation)
{
equation = equation.ToLower();

equation = standardizer.Standardize(equation);
var tokens = lexer.Parse(equation);
var result = parser.Evaluate(tokens);
equation = new Standardizer(Operators, Constants, Functions).Standardize(equation);
var tokens = new Lexer(Operators).Parse(equation);
var result = new Parser().Evaluate(tokens);

return result;
}

/// <summary>
/// Checks if <paramref name="symbols"/> contains any duplicate elements
/// or if symbols has any duplicates in <see cref="_symbols"/>.
/// </summary>
/// <param name="symbols">The symbols to check duplicates for</param>
/// <exception cref="ArgumentException">Thrown when duplicate elements are encountered.</exception>
private void CheckDuplicateSymbolsAndThrow(IEnumerable<string> symbols)
{
var duplicateSymbolsWithExisting = symbols.Intersect(_symbols);
var duplicateSymbolsInGiven = symbols.GroupBy(s => s)
.Where(s => s.Count() > 1)
.Select(s => s.Key);

var duplicates = duplicateSymbolsWithExisting.Union(duplicateSymbolsInGiven).ToArray();

if (duplicates.Length > 0)
throw new ArgumentException($"Duplicate symbols found: {string.Join(", ", duplicates)}");
}

#region Default Symbols

/// <summary>
Expand Down Expand Up @@ -124,6 +166,6 @@ public double Calculate(string equation)
DefaultFunctions.Deg,
DefaultFunctions.Rad
};

#endregion
}
5 changes: 3 additions & 2 deletions Calculator/Calculator.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>

<Authors>BLM16</Authors>
<Company>BLM16</Company>
<Copyright>Copyright © 2021 Bradley Myers. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/BLM16/Tokenized-Calculator</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>4.2.0</Version>
<Version>4.3.0</Version>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageTags>calculator; math; solve</PackageTags>
<PackageId>BLM16.Util.$(AssemblyName)</PackageId>
Expand All @@ -19,6 +19,7 @@
<RootNamespace>BLM16.Util.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
<PackageIcon>icon.png</PackageIcon>
<ApplicationIcon>icon.ico</ApplicationIcon>
<Title>Calculator</Title>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion Calculator/Lexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public List<Token> Parse(string equation)
if (op.Any())
{
var cur_op = op.First();
if (negCount > 0 && !(cur_op > DefaultOperators.Subtraction))
if (negCount > 0)
{
tokens.Add(new Token(TokenType.RBRACK, ')'));
negCount--;
Expand Down
16 changes: 14 additions & 2 deletions Calculator/Models/Constant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ public Constant(double value, params string[] symbols)
public static class DefaultConstants
{
/// <summary>
/// The constant PI
/// The constant PI. Uses the value of <see cref="Math.PI"/>.
/// </summary>
public static Constant PI
=> new(Math.PI, "pi", "π");

/// <summary>
/// Euler's number
/// The constant E. Uses the value of <see cref="Math.E"/>.
/// </summary>
public static Constant E
=> new(Math.E, "e");
Expand All @@ -52,4 +52,16 @@ public static Constant E
/// </summary>
public static Constant Phi
=> new(1.6180339887498948, "phi", "φ");

/// <summary>
/// 2π. Uses the value of <see cref="Math.Tau"/>.
/// </summary>
public static Constant Tau
=> new(Math.Tau, "tau", "τ");

/// <summary>
/// The speed of light in m/s
/// </summary>
public static Constant C
=> new(299792458, "c");
}
20 changes: 19 additions & 1 deletion Calculator/Models/Operator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,23 @@ public static Operator Division
/// The default exponent operator
/// </summary>
public static Operator Exponent
=> new('^', 30, (double num1, double exponent) => Math.Pow(num1, exponent));
=> new('^', 30, Math.Pow);

/// <summary>
/// The default modulus operator.
/// </summary>
/// <remarks>
/// The % operator in C# is the remainder operator and does not perform mathematical modulus.
/// This operator should be used if mod is desired as opposed to remainder.
/// </remarks>
public static Operator Modulus
=> new('%', 20, (double num1, double num2) =>
{
if (num2 == 0) throw new DivideByZeroException($"{num1} {Modulus.Symbol} {num2}");
if (num2 == -1) return 0;

double m = num1 % num2;
if (m < 0) m = (num2 < 0) ? m - num2 : m + num2;
return m;
});
}
Loading

0 comments on commit 026bd4c

Please sign in to comment.