diff --git a/ParseTree/ParseTree.Src/Exceptions/IncorrectExpressionExcpeption.cs b/ParseTree/ParseTree.Src/Exceptions/IncorrectExpressionExcpeption.cs new file mode 100644 index 0000000..773716f --- /dev/null +++ b/ParseTree/ParseTree.Src/Exceptions/IncorrectExpressionExcpeption.cs @@ -0,0 +1,7 @@ +namespace ParseTree; + +public class IncorrectExpressionException: Exception +{ + public IncorrectExpressionException() : base() { } + public IncorrectExpressionException(string message) : base(message) { } +} diff --git a/ParseTree/ParseTree.Src/Exceptions/IncorrectTreeException.cs b/ParseTree/ParseTree.Src/Exceptions/IncorrectTreeException.cs new file mode 100644 index 0000000..00a15bd --- /dev/null +++ b/ParseTree/ParseTree.Src/Exceptions/IncorrectTreeException.cs @@ -0,0 +1,7 @@ +namespace ParseTree; + +public class IncorrectTreeException: Exception +{ + public IncorrectTreeException() : base() { } + public IncorrectTreeException(string message) : base(message) { } +} diff --git a/ParseTree/ParseTree.Src/Nodes/INode.cs b/ParseTree/ParseTree.Src/Nodes/INode.cs new file mode 100644 index 0000000..1e80536 --- /dev/null +++ b/ParseTree/ParseTree.Src/Nodes/INode.cs @@ -0,0 +1,12 @@ +using System.Data; + +namespace ParseTree.Dependencies; + +internal interface INode +{ + double Eval(); + string ToString(); + + INode? LeftChild { get; set; } + INode? RightChild { get; set; } +} \ No newline at end of file diff --git a/ParseTree/ParseTree.Src/Nodes/NumberNode.cs b/ParseTree/ParseTree.Src/Nodes/NumberNode.cs new file mode 100644 index 0000000..2df52b6 --- /dev/null +++ b/ParseTree/ParseTree.Src/Nodes/NumberNode.cs @@ -0,0 +1,24 @@ +namespace ParseTree.Dependencies; + +internal class NumberNode : INode +{ + public double Eval() + { + return Value; + } + + public override string ToString() + { + return Value.ToString(); + } + + public NumberNode(int value) + { + Value = value; + LeftChild = null; + } + + public INode? LeftChild { get; set; } + public INode? RightChild { get; set; } + public double Value; +} diff --git a/ParseTree/ParseTree.Src/Nodes/OperationNode.cs b/ParseTree/ParseTree.Src/Nodes/OperationNode.cs new file mode 100644 index 0000000..26a1137 --- /dev/null +++ b/ParseTree/ParseTree.Src/Nodes/OperationNode.cs @@ -0,0 +1,31 @@ +namespace ParseTree.Dependencies; + +internal class OperationNode : INode +{ + public INode? LeftChild { get; set; } + public INode? RightChild { get; set; } + public char operation; + + public OperationNode(char operationChar) + { + operation = operationChar; + } + + public double Eval() + { + if (LeftChild is not null && RightChild is not null) + { + return Operation.Calculate(operation, LeftChild.Eval(), RightChild.Eval()); + } + throw new IncorrectTreeException(); + } + + public override string ToString() + { + if (LeftChild is not null && RightChild is not null) + { + return $"({operation} {LeftChild.ToString()} {RightChild.ToString()})"; + } + throw new IncorrectTreeException(); + } +} diff --git a/ParseTree/ParseTree.Src/Operation.cs b/ParseTree/ParseTree.Src/Operation.cs new file mode 100644 index 0000000..b5b2174 --- /dev/null +++ b/ParseTree/ParseTree.Src/Operation.cs @@ -0,0 +1,49 @@ +using System.Runtime.InteropServices; + +namespace ParseTree.Dependencies; + +/// +/// Class implementing arithmetic operations based on char input +/// +class Operation +{ + /// + /// Do the arithmetic operation corresponding to char + /// + /// Char '*', '+', '-' or '/' + /// Left operand + /// Right operand + /// Result of the operation + /// Thrown if operandRight is 0 and operation is '/' + /// thrown if argument operation is something different to '*', '+', '-' or '/' + public static double Calculate(char operation, double operandLeft, double operandRight) + { + switch (operation) + { + case '+': + { + return operandLeft + operandRight; + } + case '-': + { + return operandLeft - operandRight; + } + case '*': + { + return operandLeft * operandRight; + } + case '/': + { + if (operandRight == 0) + { + throw new DivideByZeroException(); + } + return operandLeft / operandRight; + } + default: + { + throw new IncorrectExpressionException(); + } + } + } +} diff --git a/ParseTree/ParseTree.Src/ParseTree.csproj b/ParseTree/ParseTree.Src/ParseTree.csproj new file mode 100644 index 0000000..206b89a --- /dev/null +++ b/ParseTree/ParseTree.Src/ParseTree.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/ParseTree/ParseTree.Src/Program.cs b/ParseTree/ParseTree.Src/Program.cs new file mode 100644 index 0000000..5b92bd9 --- /dev/null +++ b/ParseTree/ParseTree.Src/Program.cs @@ -0,0 +1,132 @@ +using System.Linq.Expressions; +using ParseTree; + +string options = """ + 0 - Exit + 1 - Create tree + 2 - Evaluate tree + 3 - Save tree to file + 4 - Print expression + Write number to console to choose option: + """; +bool running = true; +Tree? currentTree = null; +while (running) +{ + Console.WriteLine(options); + int userInput; + while (!int.TryParse(Console.ReadLine(), out userInput) || userInput > 4 || userInput < 0) + { + Console.WriteLine("Incorrect input"); + Console.WriteLine(options); + } + + switch (userInput) + { + case 0: + { + Console.Write("Exit"); + running = false; + break; + } + case 1: + { + Console.WriteLine("Write your ariphmetical expression in postfix form to console"); + string? expression = Console.ReadLine(); + try + { + if (expression is not null) + { + currentTree = new(expression); + } + } + catch (IncorrectExpressionException) + { + Console.WriteLine("Incorrect input\nYour expression must fit the template ( ) where operands may be expressions themselves"); + } + break; + } + case 2: + { + try + { + if (currentTree is not null) + { + var answer = currentTree.Evaluate(); + Console.WriteLine($"Answer to your expression is {answer}"); + } + else + { + Console.WriteLine("You haven't added an expression yet"); + } + } + catch (IncorrectExpressionException) + { + Console.WriteLine("Seems like your expression was incorrect. Please write another one"); + } + catch (IncorrectTreeException) + { + Console.WriteLine("Seems like there is a problem with your tree"); + } + break; + } + case 3: + { + Console.WriteLine("Write name for the file where you want to save your expression"); + if (currentTree is not null) + { + string? filename = Console.ReadLine(); + if (filename is not null && filename != string.Empty) + { + if (filename[0] != '/' || filename[0] != '~' || filename[0] != '.') + { + filename = "./" + filename; + } + try + { + string? expression = currentTree.ToString(); + File.WriteAllText(filename, expression); + Console.Write("Your expression was saved at "); + Console.WriteLine(filename); + } + catch (IncorrectTreeException) + { + Console.WriteLine("Seems like there is a problem with your tree"); + } + + } + else + { + Console.WriteLine("You should write a filename for your file"); + } + } + else + { + Console.WriteLine("You haven't added an expression yet"); + } + break; + } + case 4: + { + if (currentTree is not null) + { + try + { + string? expression = currentTree.ToString(); + Console.Write("Your expression: "); + Console.WriteLine(expression); + } + catch (IncorrectTreeException) + { + Console.WriteLine("Seems like there is a problem with your tree"); + } + } + else + { + Console.WriteLine("You haven't added an expression yet"); + } + break; + } + } + Console.WriteLine(); +} diff --git a/ParseTree/ParseTree.Src/Tree.cs b/ParseTree/ParseTree.Src/Tree.cs new file mode 100644 index 0000000..50f3452 --- /dev/null +++ b/ParseTree/ParseTree.Src/Tree.cs @@ -0,0 +1,118 @@ +using System.ComponentModel; +using System.Globalization; +using System.Linq.Expressions; +using ParseTree.Dependencies; + +namespace ParseTree; + +/// +/// Data structure for storing ariphmetical expressions +/// +public class Tree +{ + private INode root; + + public Tree(string expression) + { + if (expression is null || expression == string.Empty) + { + throw new IncorrectExpressionException("Empty expression"); + } + + int index = 0; + SkipSpaces(expression, ref index); + if (expression[index] >= '0' && expression[index] <= '9') + { + int number = ParseNumber(expression, ref index); + if (index != expression.Length) + { + throw new IncorrectExpressionException(); + } + root = new NumberNode(number); + } + else + { + root = Parse(expression, ref index); + } + } + + private static INode Parse(string expression, ref int index) + { + INode newNode; + SkipSpaces(expression, ref index); + char currentChar = expression[index]; + if (currentChar == '(') + { + ++index; + SkipSpaces(expression, ref index); + newNode = new OperationNode(expression[index]); + ++index; + newNode.LeftChild = Parse(expression, ref index); + newNode.RightChild = Parse(expression, ref index); + SkipSpaces(expression, ref index); + if (expression[index] == ')') + { + ++index; + } + else + { + throw new IncorrectExpressionException(); + } + } + else if (currentChar >= '0' && currentChar <= '9') + { + int number = ParseNumber(expression, ref index); + newNode = new NumberNode(number); + } + else + { + throw new IncorrectExpressionException(); + } + + return newNode; + } + + private static void SkipSpaces(string expression, ref int index) + { + while (expression[index] == ' ') + { + ++index; + } + } + + private static int ParseNumber(string expression, ref int index) + { + int currentNumber = 0; + char currentChar = expression[index]; + while (currentChar >= '0' && currentChar <= '9') + { + currentNumber *= 10; + currentNumber += expression[index] - '0'; + ++index; + if (currentChar >= expression.Length) + { + break; + } + currentChar = expression[index]; + } + return currentNumber; + } + + /// + /// Evaluate the expression stored in the tree + /// + /// Answer to expression + public double Evaluate() + { + return root.Eval(); + } + + /// + /// Write the expression in postfix form to string + /// + /// + public override string ToString() + { + return root.ToString(); + } +} diff --git a/ParseTree/ParseTree.Tests/ParseTree.Tests.cs b/ParseTree/ParseTree.Tests/ParseTree.Tests.cs new file mode 100644 index 0000000..f51bf49 --- /dev/null +++ b/ParseTree/ParseTree.Tests/ParseTree.Tests.cs @@ -0,0 +1,39 @@ +namespace ParseTree.Tests; + +using System.Data; +using ParseTree; + +public class Tests +{ + [TestCase("( * (+ 1 1 ) 2 )", 4)] + [TestCase("5", 5)] + [TestCase("(/ 5 2)", 2.5)] + public void TestEvaluation(string expression, double expectedResult) + { + var tree = new Tree(expression); + double result = tree.Evaluate(); + + Assert.That(result, Is.EqualTo(expectedResult)); + } + + [TestCase("")] + [TestCase("]")] + [TestCase("7 7")] + [TestCase("+ 5 5")] + public void IncorrectInputTest(string expression) + { + Assert.Throws(() => new Tree(expression)); + } + + [TestCase("(* (+ 1 1) 2)")] + [TestCase("5")] + [TestCase("(/ 5 2)")] + public void ToStringTests(string expression) + { + var tree = new Tree(expression); + var result = tree.ToString(); + + Assert.That(result, Is.EqualTo(expression)); + } +} + \ No newline at end of file diff --git a/ParseTree/ParseTree.Tests/ParseTree.Tests.csproj b/ParseTree/ParseTree.Tests/ParseTree.Tests.csproj new file mode 100644 index 0000000..5d1d206 --- /dev/null +++ b/ParseTree/ParseTree.Tests/ParseTree.Tests.csproj @@ -0,0 +1,28 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + diff --git a/ParseTree/ParseTree.sln b/ParseTree/ParseTree.sln new file mode 100644 index 0000000..d8d211c --- /dev/null +++ b/ParseTree/ParseTree.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParseTree", "ParseTree.Src\ParseTree.csproj", "{70E2E29F-DAE1-43F1-BF14-B9FB100883D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParseTree.Tests", "ParseTree.Tests\ParseTree.Tests.csproj", "{908939E8-E73B-4C6E-A73B-A04188F9BE0A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {70E2E29F-DAE1-43F1-BF14-B9FB100883D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70E2E29F-DAE1-43F1-BF14-B9FB100883D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70E2E29F-DAE1-43F1-BF14-B9FB100883D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70E2E29F-DAE1-43F1-BF14-B9FB100883D5}.Release|Any CPU.Build.0 = Release|Any CPU + {908939E8-E73B-4C6E-A73B-A04188F9BE0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {908939E8-E73B-4C6E-A73B-A04188F9BE0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {908939E8-E73B-4C6E-A73B-A04188F9BE0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {908939E8-E73B-4C6E-A73B-A04188F9BE0A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal