From ae5a768ae871c6cd28874c47c470bd27c2def770 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Mon, 15 Sep 2025 17:32:20 +0300 Subject: [PATCH 01/14] init proj --- HW2/HW2.sln | 22 ++++++++++++++++++ HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj | 24 ++++++++++++++++++++ HW2/SyntaxTree.Tests/UnitTest1.cs | 15 ++++++++++++ HW2/SyntaxTree/Program.cs | 3 +++ HW2/SyntaxTree/SyntaxTree.csproj | 10 ++++++++ HW2/global.json | 7 ++++++ 6 files changed, 81 insertions(+) create mode 100644 HW2/HW2.sln create mode 100644 HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj create mode 100644 HW2/SyntaxTree.Tests/UnitTest1.cs create mode 100644 HW2/SyntaxTree/Program.cs create mode 100644 HW2/SyntaxTree/SyntaxTree.csproj create mode 100644 HW2/global.json diff --git a/HW2/HW2.sln b/HW2/HW2.sln new file mode 100644 index 0000000..c5d8b5c --- /dev/null +++ b/HW2/HW2.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntaxTree", "SyntaxTree\SyntaxTree.csproj", "{6C1B7790-DBF9-4146-A87B-83D95A0D5C54}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SyntaxTree.Tests", "SyntaxTree.Tests\SyntaxTree.Tests.csproj", "{29D5F19D-35BC-49BE-8724-369258B5717D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6C1B7790-DBF9-4146-A87B-83D95A0D5C54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6C1B7790-DBF9-4146-A87B-83D95A0D5C54}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6C1B7790-DBF9-4146-A87B-83D95A0D5C54}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6C1B7790-DBF9-4146-A87B-83D95A0D5C54}.Release|Any CPU.Build.0 = Release|Any CPU + {29D5F19D-35BC-49BE-8724-369258B5717D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29D5F19D-35BC-49BE-8724-369258B5717D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29D5F19D-35BC-49BE-8724-369258B5717D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29D5F19D-35BC-49BE-8724-369258B5717D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj new file mode 100644 index 0000000..39c01e6 --- /dev/null +++ b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj @@ -0,0 +1,24 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/HW2/SyntaxTree.Tests/UnitTest1.cs b/HW2/SyntaxTree.Tests/UnitTest1.cs new file mode 100644 index 0000000..995beff --- /dev/null +++ b/HW2/SyntaxTree.Tests/UnitTest1.cs @@ -0,0 +1,15 @@ +namespace SyntaxTree.Tests; + +public class Tests +{ + [SetUp] + public void Setup() + { + } + + [Test] + public void Test1() + { + Assert.Pass(); + } +} \ No newline at end of file diff --git a/HW2/SyntaxTree/Program.cs b/HW2/SyntaxTree/Program.cs new file mode 100644 index 0000000..e5dff12 --- /dev/null +++ b/HW2/SyntaxTree/Program.cs @@ -0,0 +1,3 @@ +// See https://aka.ms/new-console-template for more information + +Console.WriteLine("Hello, World!"); \ No newline at end of file diff --git a/HW2/SyntaxTree/SyntaxTree.csproj b/HW2/SyntaxTree/SyntaxTree.csproj new file mode 100644 index 0000000..2f4fc77 --- /dev/null +++ b/HW2/SyntaxTree/SyntaxTree.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/HW2/global.json b/HW2/global.json new file mode 100644 index 0000000..2ddda36 --- /dev/null +++ b/HW2/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestMinor", + "allowPrerelease": false + } +} \ No newline at end of file From 901b77870a25f991a8e47a1b1ff3194c52f75a4d Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Wed, 17 Sep 2025 23:30:17 +0300 Subject: [PATCH 02/14] Implement BinaryOperationNode and NumberNode --- HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj | 11 ++++ HW2/SyntaxTree.Tests/UnitTest1.cs | 4 ++ HW2/SyntaxTree.Tests/stylecop.json | 9 +++ HW2/SyntaxTree/AbstractNode.cs | 23 ++++++++ HW2/SyntaxTree/BinaryOperationNode.cs | 60 ++++++++++++++++++++ HW2/SyntaxTree/NumberNode.cs | 25 ++++++++ HW2/SyntaxTree/Program.cs | 4 +- HW2/SyntaxTree/SyntaxTree.csproj | 12 ++++ HW2/SyntaxTree/stylecop.json | 9 +++ 9 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 HW2/SyntaxTree.Tests/stylecop.json create mode 100644 HW2/SyntaxTree/AbstractNode.cs create mode 100644 HW2/SyntaxTree/BinaryOperationNode.cs create mode 100644 HW2/SyntaxTree/NumberNode.cs create mode 100644 HW2/SyntaxTree/stylecop.json diff --git a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj index 39c01e6..5acf215 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj +++ b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj @@ -21,4 +21,15 @@ + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/HW2/SyntaxTree.Tests/UnitTest1.cs b/HW2/SyntaxTree.Tests/UnitTest1.cs index 995beff..fd67ba6 100644 --- a/HW2/SyntaxTree.Tests/UnitTest1.cs +++ b/HW2/SyntaxTree.Tests/UnitTest1.cs @@ -1,3 +1,7 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + namespace SyntaxTree.Tests; public class Tests diff --git a/HW2/SyntaxTree.Tests/stylecop.json b/HW2/SyntaxTree.Tests/stylecop.json new file mode 100644 index 0000000..76c8e76 --- /dev/null +++ b/HW2/SyntaxTree.Tests/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "khusainovilas", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} \ No newline at end of file diff --git a/HW2/SyntaxTree/AbstractNode.cs b/HW2/SyntaxTree/AbstractNode.cs new file mode 100644 index 0000000..4127f76 --- /dev/null +++ b/HW2/SyntaxTree/AbstractNode.cs @@ -0,0 +1,23 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Abstract node of the syntax tree. +/// +public abstract class AbstractNode +{ + /// + /// Calculates the value of the node. + /// + /// The calculated result. + public abstract int Calculate(); + + /// + /// Returns the string representation of the node. + /// + /// String representation of the node. + public abstract string ToStringRepresentation(); +} \ No newline at end of file diff --git a/HW2/SyntaxTree/BinaryOperationNode.cs b/HW2/SyntaxTree/BinaryOperationNode.cs new file mode 100644 index 0000000..f1f3cbf --- /dev/null +++ b/HW2/SyntaxTree/BinaryOperationNode.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Represents a binary operation node in the syntax tree. +/// +public class BinaryOperationNode : AbstractNode +{ + private readonly char operation; + private readonly AbstractNode leftNode; + private readonly AbstractNode rightNode; + + /// + /// Initializes a new instance of the class. + /// + /// The operation character (+, -, *, /). + /// The left operand node. + /// The right operand node. + public BinaryOperationNode(char operation, AbstractNode leftNode, AbstractNode rightNode) + { + this.operation = operation; + this.leftNode = leftNode ?? throw new ArgumentNullException(nameof(leftNode)); + this.rightNode = rightNode ?? throw new ArgumentNullException(nameof(rightNode)); + this.Validate(); + } + + private void Validate() + { + if (!"+-*/".Contains(this.operation)) + { + throw new ArgumentException("Invalid operator. Supported: +, -, *, /.", nameof(this.operation)); + } + } + + /// + public override int Calculate() + { + var leftValue = this.leftNode.Calculate(); + var rightValue = this.rightNode.Calculate(); + + return this.operation switch + { + '+' => leftValue + rightValue, + '-' => leftValue - rightValue, + '*' => leftValue * rightValue, + '/' => rightValue != 0 ? leftValue / rightValue : throw new DivideByZeroException("Division by zero."), + _ => throw new InvalidOperationException("Unsupported operation."), + }; + } + + /// + public override string ToStringRepresentation() + { + return + $"({this.operation} {this.leftNode.ToStringRepresentation()} {this.rightNode.ToStringRepresentation()})"; + } +} \ No newline at end of file diff --git a/HW2/SyntaxTree/NumberNode.cs b/HW2/SyntaxTree/NumberNode.cs new file mode 100644 index 0000000..32f2abf --- /dev/null +++ b/HW2/SyntaxTree/NumberNode.cs @@ -0,0 +1,25 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Represents a number node in the syntax tree. +/// +public class NumberNode(int value) : AbstractNode +{ + private readonly int value = value; + + /// + public override int Calculate() + { + return this.value; + } + + /// + public override string ToStringRepresentation() + { + return this.value.ToString(); + } +} \ No newline at end of file diff --git a/HW2/SyntaxTree/Program.cs b/HW2/SyntaxTree/Program.cs index e5dff12..b1e43ef 100644 --- a/HW2/SyntaxTree/Program.cs +++ b/HW2/SyntaxTree/Program.cs @@ -1,3 +1,3 @@ -// See https://aka.ms/new-console-template for more information +namespace SyntaxTree; -Console.WriteLine("Hello, World!"); \ No newline at end of file +public class Program; \ No newline at end of file diff --git a/HW2/SyntaxTree/SyntaxTree.csproj b/HW2/SyntaxTree/SyntaxTree.csproj index 2f4fc77..342775e 100644 --- a/HW2/SyntaxTree/SyntaxTree.csproj +++ b/HW2/SyntaxTree/SyntaxTree.csproj @@ -7,4 +7,16 @@ enable + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/HW2/SyntaxTree/stylecop.json b/HW2/SyntaxTree/stylecop.json new file mode 100644 index 0000000..76c8e76 --- /dev/null +++ b/HW2/SyntaxTree/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "khusainovilas", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} \ No newline at end of file From 65884e3a6cad90771ea613f03b16cae893eeec7c Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 21:41:09 +0300 Subject: [PATCH 03/14] complete implementation of syntax tree --- HW2/SyntaxTree/NumberNode.cs | 6 +- HW2/SyntaxTree/Parser.cs | 186 +++++++++++++++++++++++++++++++++++ HW2/SyntaxTree/Program.cs | 55 ++++++++++- 3 files changed, 241 insertions(+), 6 deletions(-) create mode 100644 HW2/SyntaxTree/Parser.cs diff --git a/HW2/SyntaxTree/NumberNode.cs b/HW2/SyntaxTree/NumberNode.cs index 32f2abf..7c15b0a 100644 --- a/HW2/SyntaxTree/NumberNode.cs +++ b/HW2/SyntaxTree/NumberNode.cs @@ -9,17 +9,15 @@ namespace SyntaxTree; /// public class NumberNode(int value) : AbstractNode { - private readonly int value = value; - /// public override int Calculate() { - return this.value; + return value; } /// public override string ToStringRepresentation() { - return this.value.ToString(); + return value.ToString(); } } \ No newline at end of file diff --git a/HW2/SyntaxTree/Parser.cs b/HW2/SyntaxTree/Parser.cs new file mode 100644 index 0000000..a2d6add --- /dev/null +++ b/HW2/SyntaxTree/Parser.cs @@ -0,0 +1,186 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Parser for building the syntax tree from a string representation. +/// +public static class Parser +{ + /// + /// Parses the input string into a syntax tree node. + /// + /// The input string in prefix notation. + /// The root node of the syntax tree. + public static AbstractNode Parse(string input) + { + if (string.IsNullOrWhiteSpace(input)) + { + throw new ArgumentException("Input cannot be empty or whitespace."); + } + + var tokens = Tokenize(input); + var index = 0; + var result = ParseExpression(tokens, ref index); + + if (index != tokens.Count) + { + throw new ArgumentException("Extra tokens after parsing complete expression."); + } + + return result; + } + + /// + /// Tokenizes the input string into a list of tokens. + /// + /// The input string to tokenize. + /// A list of tokens. + private static List Tokenize(string input) + { + var tokens = new List(); + var currentIndex = 0; + + while (currentIndex < input.Length) + { + var currentChar = input[currentIndex]; + + if (char.IsWhiteSpace(currentChar)) + { + currentIndex++; + continue; + } + + switch (currentChar) + { + case '(': + case ')': + case '+': + case '*': + case '/': + tokens.Add(currentChar.ToString()); + currentIndex++; + continue; + + case '-': + { + if (currentIndex + 1 < input.Length && char.IsDigit(input[currentIndex + 1])) + { + var start = currentIndex; + currentIndex++; + while (currentIndex < input.Length && char.IsDigit(input[currentIndex])) + { + currentIndex++; + } + + tokens.Add(input.Substring(start, currentIndex - start)); + } + else + { + tokens.Add(currentChar.ToString()); + currentIndex++; + } + + continue; + } + } + + if (char.IsDigit(currentChar)) + { + var start = currentIndex; + while (currentIndex < input.Length && char.IsDigit(input[currentIndex])) + { + currentIndex++; + } + + tokens.Add(input.Substring(start, currentIndex - start)); + continue; + } + + throw new ArgumentException($"Invalid character '{currentChar}' at position {currentIndex}."); + } + + return tokens; + } + + // + // Parse a list of tokens. + // + // The list of tokens. + // The current token index. + // The parsed node. + private static AbstractNode ParseExpression(List tokens, ref int index) + { + var operators = new Stack(); + var nodes = new Stack(); + + while (index < tokens.Count) + { + var token = tokens[index]; + + if (int.TryParse(token, out var value)) + { + nodes.Push(new NumberNode(value)); + index++; + } + else if (token == "(") + { + operators.Push(token); + index++; + } + else if ("+-*/".Contains(token)) + { + if (operators.Count == 0 || operators.Peek() != "(") + { + throw new ArgumentException($"Expected '(', found '{token}' at position {index}."); + } + + operators.Push(token); + index++; + } + else if (token == ")") + { + if (operators.Count < 2 || operators.Peek() == "(") + { + throw new ArgumentException($"Missing operation before ')' at position {index}."); + } + + var op = operators.Pop(); + if (operators.Peek() != "(") + { + throw new ArgumentException($"Missing opening '(' before position {index}."); + } + + operators.Pop(); + + if (nodes.Count < 2) + { + throw new ArgumentException($"Missing operands for operation '{op}' at position {index}."); + } + + var right = nodes.Pop(); + var left = nodes.Pop(); + nodes.Push(new BinaryOperationNode(op[0], left, right)); + index++; + } + else + { + throw new ArgumentException($"Unexpected token '{token}' at position {index}."); + } + } + + if (operators.Count > 0) + { + throw new ArgumentException("Missing closing ')'."); + } + + if (nodes.Count != 1) + { + throw new ArgumentException("Incomplete expression."); + } + + return nodes.Pop(); + } +} diff --git a/HW2/SyntaxTree/Program.cs b/HW2/SyntaxTree/Program.cs index b1e43ef..642df52 100644 --- a/HW2/SyntaxTree/Program.cs +++ b/HW2/SyntaxTree/Program.cs @@ -1,3 +1,54 @@ -namespace SyntaxTree; +// +// Copyright (c) khusainovilas. All rights reserved. +// -public class Program; \ No newline at end of file +namespace SyntaxTree; + +/// +/// Main class for the syntax tree parsing program. +/// +public static class Program +{ + /// + /// The main method of processing the input file and output of the results. + /// + /// Command line arguments. + public static void Main(string[] args) + { + try + { + if (args.Length != 1) + { + throw new ArgumentException("Exactly one argument is expected - the file path."); + } + + var filePath = args[0]; + if (!File.Exists(filePath)) + { + throw new FileNotFoundException("File not found.", filePath); + } + + var input = File.ReadAllText(filePath).Trim(); + var tree = Parser.Parse(input); + + Console.WriteLine($"The tree: {tree.ToStringRepresentation()}"); + Console.WriteLine($"The result: {tree.Calculate()}"); + } + catch (FileNotFoundException ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + catch (ArgumentException ex) + { + Console.WriteLine($"Error: Incorrect expression. {ex.Message}"); + } + catch (DivideByZeroException) + { + Console.WriteLine("Mistake: Division by zero."); + } + catch (Exception) + { + Console.WriteLine("Error: An unknown error has occurred."); + } + } +} \ No newline at end of file From f638485c58c5fc9d41c6a56d24bc823c9f80b9c1 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 21:46:15 +0300 Subject: [PATCH 04/14] fix stylecop violations --- HW2/SyntaxTree/BinaryOperationNode.cs | 16 ++++++++-------- HW2/global.json | 7 ------- 2 files changed, 8 insertions(+), 15 deletions(-) delete mode 100644 HW2/global.json diff --git a/HW2/SyntaxTree/BinaryOperationNode.cs b/HW2/SyntaxTree/BinaryOperationNode.cs index f1f3cbf..5b715cf 100644 --- a/HW2/SyntaxTree/BinaryOperationNode.cs +++ b/HW2/SyntaxTree/BinaryOperationNode.cs @@ -27,14 +27,6 @@ public BinaryOperationNode(char operation, AbstractNode leftNode, AbstractNode r this.Validate(); } - private void Validate() - { - if (!"+-*/".Contains(this.operation)) - { - throw new ArgumentException("Invalid operator. Supported: +, -, *, /.", nameof(this.operation)); - } - } - /// public override int Calculate() { @@ -57,4 +49,12 @@ public override string ToStringRepresentation() return $"({this.operation} {this.leftNode.ToStringRepresentation()} {this.rightNode.ToStringRepresentation()})"; } + + private void Validate() + { + if (!"+-*/".Contains(this.operation)) + { + throw new ArgumentException("Invalid operator. Supported: +, -, *, /.", nameof(this.operation)); + } + } } \ No newline at end of file diff --git a/HW2/global.json b/HW2/global.json deleted file mode 100644 index 2ddda36..0000000 --- a/HW2/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "8.0.0", - "rollForward": "latestMinor", - "allowPrerelease": false - } -} \ No newline at end of file From ca15fc1f75617d73bfe1ca2adc140d929e79ab12 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 22:16:17 +0300 Subject: [PATCH 05/14] add NUnit tests --- HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj | 4 + HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 124 +++++++++++++++++++ HW2/SyntaxTree.Tests/UnitTest1.cs | 19 --- HW2/SyntaxTree.Tests/testFile.txt | 1 + 4 files changed, 129 insertions(+), 19 deletions(-) create mode 100644 HW2/SyntaxTree.Tests/SyntaxTreeTests.cs delete mode 100644 HW2/SyntaxTree.Tests/UnitTest1.cs create mode 100644 HW2/SyntaxTree.Tests/testFile.txt diff --git a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj index 5acf215..4ed6064 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj +++ b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj @@ -32,4 +32,8 @@ + + + + diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs new file mode 100644 index 0000000..008e525 --- /dev/null +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree.Tests; + +/// +/// tests for syntax tree. +/// +public class SyntaxTreeTests +{ + private StringWriter consoleOutput; + + // Set up common test environment before each test. + [SetUp] + public void SetUp() + { + this.consoleOutput = new StringWriter(); + Console.SetOut(this.consoleOutput); + } + + // Clean up after each test. + [TearDown] + public void TearDown() + { + this.consoleOutput.Dispose(); + Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true }); + } + + // Test parsing a valid expression with multiplication and addition. + [Test] + public void Parser_Parse_ValidExpression_ReturnsCorrectTreeAndResult() + { + const string input = "(* (+ 1 1) 2)"; + var tree = Parser.Parse(input); + Assert.Multiple(() => + { + Assert.That(tree.ToStringRepresentation(), Is.EqualTo("(* (+ 1 1) 2)")); + Assert.That(tree.Calculate(), Is.EqualTo(4)); + }); + } + + // Test parsing an expression with negative numbers. + [Test] + public void Parser_Parse_NegativeNumber_ReturnsCorrectResult() + { + const string input = "(+ -5 3)"; + var tree = Parser.Parse(input); + Assert.Multiple(() => + { + Assert.That(tree.ToStringRepresentation(), Is.EqualTo("(+ -5 3)")); + Assert.That(tree.Calculate(), Is.EqualTo(-2)); + }); + } + + // Test parsing an empty input throws ArgumentException. + [Test] + public void Parser_Parse_EmptyInput_ThrowsArgumentException() + { + Assert.Throws(() => Parser.Parse(string.Empty)); + } + + // Test parsing an invalid token throws ArgumentException. + [Test] + public void Parser_Parse_InvalidToken_ThrowsArgumentException() + { + Assert.Throws(() => Parser.Parse("(+ 1 a)")); + } + + // Test parsing unbalanced parentheses throws ArgumentException. + [Test] + public void Parser_Parse_UnbalancedParentheses_ThrowsArgumentException() + { + Assert.Throws(() => Parser.Parse("(( 1 2)")); + } + + // Test parsing division by zero throws DivideByZeroException in Calculate. + [Test] + public void Parser_Parse_DivisionByZero_ThrowsDivideByZeroException() + { + const string input = "(/ 10 0)"; + var tree = Parser.Parse(input); + Assert.That(tree.ToStringRepresentation(), Is.EqualTo("(/ 10 0)")); + Assert.Throws(() => tree.Calculate()); + } + + // Test NumberNode calculation and string representation. + [Test] + public void NumberNode_CalculateAndToString_ReturnsCorrectValues() + { + var node = new NumberNode(-5); + Assert.Multiple(() => + { + Assert.That(node.Calculate(), Is.EqualTo(-5)); + Assert.That(node.ToStringRepresentation(), Is.EqualTo("-5")); + }); + } + + // Test BinaryOperationNode calculation and string representation. + [Test] + public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() + { + var node = new BinaryOperationNode('+', new NumberNode(1), new NumberNode(2)); + Assert.Multiple(() => + { + Assert.That(node.Calculate(), Is.EqualTo(3)); + Assert.That(node.ToStringRepresentation(), Is.EqualTo("(+ 1 2)")); + }); + } + + // Test Program.Main with testFile.txt containing valid expression. + [Test] + public void Program_Main_ValidFileOutputs() + { + const string filePath = "testFile.txt"; + Program.Main(new[] { filePath }); + var output = this.consoleOutput.ToString().Trim(); + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("The tree: (+ 1 2)")); + Assert.That(output, Does.Contain("The result: 3")); + }); + } +} \ No newline at end of file diff --git a/HW2/SyntaxTree.Tests/UnitTest1.cs b/HW2/SyntaxTree.Tests/UnitTest1.cs deleted file mode 100644 index fd67ba6..0000000 --- a/HW2/SyntaxTree.Tests/UnitTest1.cs +++ /dev/null @@ -1,19 +0,0 @@ -// -// Copyright (c) khusainovilas. All rights reserved. -// - -namespace SyntaxTree.Tests; - -public class Tests -{ - [SetUp] - public void Setup() - { - } - - [Test] - public void Test1() - { - Assert.Pass(); - } -} \ No newline at end of file diff --git a/HW2/SyntaxTree.Tests/testFile.txt b/HW2/SyntaxTree.Tests/testFile.txt new file mode 100644 index 0000000..fbd556d --- /dev/null +++ b/HW2/SyntaxTree.Tests/testFile.txt @@ -0,0 +1 @@ +(+ 1 2) \ No newline at end of file From 1f7b99f1dcd79b5f31bc1b1dabb67f9d988d05ec Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 22:27:08 +0300 Subject: [PATCH 06/14] fix tests --- HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs index 008e525..4d4b67a 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -112,7 +112,7 @@ public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() [Test] public void Program_Main_ValidFileOutputs() { - const string filePath = "testFile.txt"; + var filePath = Path.Combine(AppContext.BaseDirectory, "TestFile.txt"); Program.Main(new[] { filePath }); var output = this.consoleOutput.ToString().Trim(); Assert.Multiple(() => From ca270f698f5047a0c1056524ebdc9229a9aa7cb0 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 22:30:06 +0300 Subject: [PATCH 07/14] fix tests --- HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs index 4d4b67a..78344cf 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -112,7 +112,7 @@ public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() [Test] public void Program_Main_ValidFileOutputs() { - var filePath = Path.Combine(AppContext.BaseDirectory, "TestFile.txt"); + var filePath = Path.Combine(AppContext.BaseDirectory, "testFile.txt"); Program.Main(new[] { filePath }); var output = this.consoleOutput.ToString().Trim(); Assert.Multiple(() => From d1f18337f2cbb17f7d14216c1d9c1a016b5265b8 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 22:32:53 +0300 Subject: [PATCH 08/14] Update Program_Main_ValidFileOutputs test --- HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs index 78344cf..c989505 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -107,18 +107,4 @@ public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() Assert.That(node.ToStringRepresentation(), Is.EqualTo("(+ 1 2)")); }); } - - // Test Program.Main with testFile.txt containing valid expression. - [Test] - public void Program_Main_ValidFileOutputs() - { - var filePath = Path.Combine(AppContext.BaseDirectory, "testFile.txt"); - Program.Main(new[] { filePath }); - var output = this.consoleOutput.ToString().Trim(); - Assert.Multiple(() => - { - Assert.That(output, Does.Contain("The tree: (+ 1 2)")); - Assert.That(output, Does.Contain("The result: 3")); - }); - } } \ No newline at end of file From 78545c9b2c64de5b19f4117a14de37234690443e Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 22:38:13 +0300 Subject: [PATCH 09/14] Update Program_Main_ValidFileOutputs test --- HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs index c989505..78344cf 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -107,4 +107,18 @@ public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() Assert.That(node.ToStringRepresentation(), Is.EqualTo("(+ 1 2)")); }); } + + // Test Program.Main with testFile.txt containing valid expression. + [Test] + public void Program_Main_ValidFileOutputs() + { + var filePath = Path.Combine(AppContext.BaseDirectory, "testFile.txt"); + Program.Main(new[] { filePath }); + var output = this.consoleOutput.ToString().Trim(); + Assert.Multiple(() => + { + Assert.That(output, Does.Contain("The tree: (+ 1 2)")); + Assert.That(output, Does.Contain("The result: 3")); + }); + } } \ No newline at end of file From 471aee9e49fbf2916d543588c4753f1bc86cdfe4 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Sun, 12 Oct 2025 22:45:18 +0300 Subject: [PATCH 10/14] Update Program_Main_ValidFileOutputs test --- HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj | 7 ++++++- HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj index 4ed6064..f70a40b 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj +++ b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj @@ -35,5 +35,10 @@ - + + + + Always + + diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs index 78344cf..008e525 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -112,7 +112,7 @@ public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() [Test] public void Program_Main_ValidFileOutputs() { - var filePath = Path.Combine(AppContext.BaseDirectory, "testFile.txt"); + const string filePath = "testFile.txt"; Program.Main(new[] { filePath }); var output = this.consoleOutput.ToString().Trim(); Assert.Multiple(() => From a755d6a4b54b97999794ac5599c763b271d0e248 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Tue, 21 Oct 2025 20:25:44 +0300 Subject: [PATCH 11/14] Upgrade project to .NET 9 --- HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj | 2 +- HW2/SyntaxTree/SyntaxTree.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj index f70a40b..699997e 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj +++ b/HW2/SyntaxTree.Tests/SyntaxTree.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable diff --git a/HW2/SyntaxTree/SyntaxTree.csproj b/HW2/SyntaxTree/SyntaxTree.csproj index 342775e..f3becf9 100644 --- a/HW2/SyntaxTree/SyntaxTree.csproj +++ b/HW2/SyntaxTree/SyntaxTree.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable From 72f8d1815b7f7dba158092da21b289905d849f0f Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Thu, 27 Nov 2025 15:37:58 +0300 Subject: [PATCH 12/14] minor issues have been resolved --- HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 64 +++++++++++----- HW2/SyntaxTree/BinaryOperationNode.cs | 17 ++--- .../{AbstractNode.cs => IAbstractNode.cs} | 10 +-- HW2/SyntaxTree/NumberNode.cs | 12 +-- HW2/SyntaxTree/Parser.cs | 6 +- HW2/SyntaxTree/Program.cs | 74 ++++++++----------- 6 files changed, 96 insertions(+), 87 deletions(-) rename HW2/SyntaxTree/{AbstractNode.cs => IAbstractNode.cs} (64%) diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs index 008e525..3128b82 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -11,7 +11,6 @@ public class SyntaxTreeTests { private StringWriter consoleOutput; - // Set up common test environment before each test. [SetUp] public void SetUp() { @@ -19,7 +18,6 @@ public void SetUp() Console.SetOut(this.consoleOutput); } - // Clean up after each test. [TearDown] public void TearDown() { @@ -27,7 +25,9 @@ public void TearDown() Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true }); } - // Test parsing a valid expression with multiplication and addition. + /// + /// Test parsing a valid expression with multiplication and addition. + /// [Test] public void Parser_Parse_ValidExpression_ReturnsCorrectTreeAndResult() { @@ -40,7 +40,9 @@ public void Parser_Parse_ValidExpression_ReturnsCorrectTreeAndResult() }); } - // Test parsing an expression with negative numbers. + /// + /// Test parsing an expression with negative numbers. + /// [Test] public void Parser_Parse_NegativeNumber_ReturnsCorrectResult() { @@ -53,28 +55,36 @@ public void Parser_Parse_NegativeNumber_ReturnsCorrectResult() }); } - // Test parsing an empty input throws ArgumentException. + /// + /// Test parsing an empty input throws ArgumentException. + /// [Test] public void Parser_Parse_EmptyInput_ThrowsArgumentException() { Assert.Throws(() => Parser.Parse(string.Empty)); } - // Test parsing an invalid token throws ArgumentException. + /// + /// Test parsing an invalid token throws ArgumentException. + /// [Test] public void Parser_Parse_InvalidToken_ThrowsArgumentException() { Assert.Throws(() => Parser.Parse("(+ 1 a)")); } - // Test parsing unbalanced parentheses throws ArgumentException. + /// + /// Test parsing unbalanced parentheses throws ArgumentException. + /// [Test] public void Parser_Parse_UnbalancedParentheses_ThrowsArgumentException() { Assert.Throws(() => Parser.Parse("(( 1 2)")); } - // Test parsing division by zero throws DivideByZeroException in Calculate. + /// + /// Test parsing division by zero throws DivideByZeroException in Calculate. + /// [Test] public void Parser_Parse_DivisionByZero_ThrowsDivideByZeroException() { @@ -84,7 +94,9 @@ public void Parser_Parse_DivisionByZero_ThrowsDivideByZeroException() Assert.Throws(() => tree.Calculate()); } - // Test NumberNode calculation and string representation. + /// + /// Test NumberNode calculation and string representation. + /// [Test] public void NumberNode_CalculateAndToString_ReturnsCorrectValues() { @@ -96,7 +108,9 @@ public void NumberNode_CalculateAndToString_ReturnsCorrectValues() }); } - // Test BinaryOperationNode calculation and string representation. + /// + /// Test BinaryOperationNode calculation and string representation. + /// [Test] public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() { @@ -108,17 +122,33 @@ public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() }); } - // Test Program.Main with testFile.txt containing valid expression. + /// + /// Test subtraction (non-commutative operation) works correctly. + /// [Test] - public void Program_Main_ValidFileOutputs() + public void Parser_Parse_Subtraction_ReturnsCorrectResult() { - const string filePath = "testFile.txt"; - Program.Main(new[] { filePath }); - var output = this.consoleOutput.ToString().Trim(); + const string input = "(- 10 4)"; + var tree = Parser.Parse(input); + Assert.Multiple(() => + { + Assert.That(tree.ToStringRepresentation(), Is.EqualTo("(- 10 4)")); + Assert.That(tree.Calculate(), Is.EqualTo(6)); + }); + } + + /// + /// Test division (non-commutative operation) works correctly and doesn't swap operands. + /// + [Test] + public void Parser_Parse_Division_ReturnsCorrectResult() + { + const string input = "(/ 10 2)"; + var tree = Parser.Parse(input); Assert.Multiple(() => { - Assert.That(output, Does.Contain("The tree: (+ 1 2)")); - Assert.That(output, Does.Contain("The result: 3")); + Assert.That(tree.ToStringRepresentation(), Is.EqualTo("(/ 10 2)")); + Assert.That(tree.Calculate(), Is.EqualTo(5)); }); } } \ No newline at end of file diff --git a/HW2/SyntaxTree/BinaryOperationNode.cs b/HW2/SyntaxTree/BinaryOperationNode.cs index 5b715cf..cc6b775 100644 --- a/HW2/SyntaxTree/BinaryOperationNode.cs +++ b/HW2/SyntaxTree/BinaryOperationNode.cs @@ -7,11 +7,11 @@ namespace SyntaxTree; /// /// Represents a binary operation node in the syntax tree. /// -public class BinaryOperationNode : AbstractNode +public class BinaryOperationNode : IAbstractNode { private readonly char operation; - private readonly AbstractNode leftNode; - private readonly AbstractNode rightNode; + private readonly IAbstractNode leftNode; + private readonly IAbstractNode rightNode; /// /// Initializes a new instance of the class. @@ -19,7 +19,7 @@ public class BinaryOperationNode : AbstractNode /// The operation character (+, -, *, /). /// The left operand node. /// The right operand node. - public BinaryOperationNode(char operation, AbstractNode leftNode, AbstractNode rightNode) + public BinaryOperationNode(char operation, IAbstractNode leftNode, IAbstractNode rightNode) { this.operation = operation; this.leftNode = leftNode ?? throw new ArgumentNullException(nameof(leftNode)); @@ -28,7 +28,7 @@ public BinaryOperationNode(char operation, AbstractNode leftNode, AbstractNode r } /// - public override int Calculate() + public int Calculate() { var leftValue = this.leftNode.Calculate(); var rightValue = this.rightNode.Calculate(); @@ -44,11 +44,8 @@ public override int Calculate() } /// - public override string ToStringRepresentation() - { - return - $"({this.operation} {this.leftNode.ToStringRepresentation()} {this.rightNode.ToStringRepresentation()})"; - } + public string ToStringRepresentation() => + $"({this.operation} {this.leftNode.ToStringRepresentation()} {this.rightNode.ToStringRepresentation()})"; private void Validate() { diff --git a/HW2/SyntaxTree/AbstractNode.cs b/HW2/SyntaxTree/IAbstractNode.cs similarity index 64% rename from HW2/SyntaxTree/AbstractNode.cs rename to HW2/SyntaxTree/IAbstractNode.cs index 4127f76..e84656f 100644 --- a/HW2/SyntaxTree/AbstractNode.cs +++ b/HW2/SyntaxTree/IAbstractNode.cs @@ -1,23 +1,23 @@ -// +// // Copyright (c) khusainovilas. All rights reserved. // namespace SyntaxTree; /// -/// Abstract node of the syntax tree. +/// Represents a node in the syntax tree. /// -public abstract class AbstractNode +public interface IAbstractNode { /// /// Calculates the value of the node. /// /// The calculated result. - public abstract int Calculate(); + int Calculate(); /// /// Returns the string representation of the node. /// /// String representation of the node. - public abstract string ToStringRepresentation(); + string ToStringRepresentation(); } \ No newline at end of file diff --git a/HW2/SyntaxTree/NumberNode.cs b/HW2/SyntaxTree/NumberNode.cs index 7c15b0a..2a9586e 100644 --- a/HW2/SyntaxTree/NumberNode.cs +++ b/HW2/SyntaxTree/NumberNode.cs @@ -7,17 +7,11 @@ namespace SyntaxTree; /// /// Represents a number node in the syntax tree. /// -public class NumberNode(int value) : AbstractNode +public class NumberNode(int value) : IAbstractNode { /// - public override int Calculate() - { - return value; - } + public int Calculate() => value; /// - public override string ToStringRepresentation() - { - return value.ToString(); - } + public string ToStringRepresentation() => value.ToString(); } \ No newline at end of file diff --git a/HW2/SyntaxTree/Parser.cs b/HW2/SyntaxTree/Parser.cs index a2d6add..3f60ab4 100644 --- a/HW2/SyntaxTree/Parser.cs +++ b/HW2/SyntaxTree/Parser.cs @@ -14,7 +14,7 @@ public static class Parser /// /// The input string in prefix notation. /// The root node of the syntax tree. - public static AbstractNode Parse(string input) + public static IAbstractNode Parse(string input) { if (string.IsNullOrWhiteSpace(input)) { @@ -111,10 +111,10 @@ private static List Tokenize(string input) // The list of tokens. // The current token index. // The parsed node. - private static AbstractNode ParseExpression(List tokens, ref int index) + private static IAbstractNode ParseExpression(List tokens, ref int index) { var operators = new Stack(); - var nodes = new Stack(); + var nodes = new Stack(); while (index < tokens.Count) { diff --git a/HW2/SyntaxTree/Program.cs b/HW2/SyntaxTree/Program.cs index 642df52..71cd4be 100644 --- a/HW2/SyntaxTree/Program.cs +++ b/HW2/SyntaxTree/Program.cs @@ -2,53 +2,41 @@ // Copyright (c) khusainovilas. All rights reserved. // -namespace SyntaxTree; +using SyntaxTree; -/// -/// Main class for the syntax tree parsing program. -/// -public static class Program +try { - /// - /// The main method of processing the input file and output of the results. - /// - /// Command line arguments. - public static void Main(string[] args) + if (args.Length != 1) { - try - { - if (args.Length != 1) - { - throw new ArgumentException("Exactly one argument is expected - the file path."); - } - - var filePath = args[0]; - if (!File.Exists(filePath)) - { - throw new FileNotFoundException("File not found.", filePath); - } + throw new ArgumentException("Exactly one argument is expected - the file path to the file."); + } - var input = File.ReadAllText(filePath).Trim(); - var tree = Parser.Parse(input); + var filePath = args[0]; - Console.WriteLine($"The tree: {tree.ToStringRepresentation()}"); - Console.WriteLine($"The result: {tree.Calculate()}"); - } - catch (FileNotFoundException ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - catch (ArgumentException ex) - { - Console.WriteLine($"Error: Incorrect expression. {ex.Message}"); - } - catch (DivideByZeroException) - { - Console.WriteLine("Mistake: Division by zero."); - } - catch (Exception) - { - Console.WriteLine("Error: An unknown error has occurred."); - } + if (!File.Exists(filePath)) + { + throw new FileNotFoundException("File not found.", filePath); } + + var input = File.ReadAllText(filePath).Trim(); + var tree = Parser.Parse(input); + + Console.WriteLine($"The tree: {tree.ToStringRepresentation()}"); + Console.WriteLine($"The result: {tree.Calculate()}"); +} +catch (FileNotFoundException ex) +{ + Console.WriteLine($"Error: {ex.Message}"); +} +catch (ArgumentException ex) +{ + Console.WriteLine($"Error: Incorrect expression. {ex.Message}"); +} +catch (DivideByZeroException) +{ + Console.WriteLine("Mistake: Division by zero."); +} +catch (Exception) +{ + Console.WriteLine("Error: An unknown error has occurred."); } \ No newline at end of file From ebb1c0a52a7b1b817ed48dbbd98bcd45c2dc4255 Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Thu, 27 Nov 2025 17:49:09 +0300 Subject: [PATCH 13/14] replace parentheses with spaces --- HW2/SyntaxTree/Parser.cs | 177 ++++++++++----------------------------- 1 file changed, 42 insertions(+), 135 deletions(-) diff --git a/HW2/SyntaxTree/Parser.cs b/HW2/SyntaxTree/Parser.cs index 3f60ab4..60a527a 100644 --- a/HW2/SyntaxTree/Parser.cs +++ b/HW2/SyntaxTree/Parser.cs @@ -21,166 +21,73 @@ public static IAbstractNode Parse(string input) throw new ArgumentException("Input cannot be empty or whitespace."); } - var tokens = Tokenize(input); - var index = 0; - var result = ParseExpression(tokens, ref index); + if (!AreParenthesesBalanced(input)) + { + throw new ArgumentException("Unbalanced parentheses."); + } + + var tokens = input + .Replace('(', ' ') + .Replace(')', ' ') + .Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (index != tokens.Count) + var pos = 0; + var node = ParseExpression(tokens, ref pos); + + if (pos != tokens.Length) { throw new ArgumentException("Extra tokens after parsing complete expression."); } - return result; + return node; } - /// - /// Tokenizes the input string into a list of tokens. - /// - /// The input string to tokenize. - /// A list of tokens. - private static List Tokenize(string input) + private static IAbstractNode ParseExpression(string[] tokens, ref int pos) { - var tokens = new List(); - var currentIndex = 0; - - while (currentIndex < input.Length) + if (pos >= tokens.Length) { - var currentChar = input[currentIndex]; - - if (char.IsWhiteSpace(currentChar)) - { - currentIndex++; - continue; - } - - switch (currentChar) - { - case '(': - case ')': - case '+': - case '*': - case '/': - tokens.Add(currentChar.ToString()); - currentIndex++; - continue; - - case '-': - { - if (currentIndex + 1 < input.Length && char.IsDigit(input[currentIndex + 1])) - { - var start = currentIndex; - currentIndex++; - while (currentIndex < input.Length && char.IsDigit(input[currentIndex])) - { - currentIndex++; - } - - tokens.Add(input.Substring(start, currentIndex - start)); - } - else - { - tokens.Add(currentChar.ToString()); - currentIndex++; - } - - continue; - } - } + throw new ArgumentException("Unexpected end of input."); + } - if (char.IsDigit(currentChar)) - { - var start = currentIndex; - while (currentIndex < input.Length && char.IsDigit(input[currentIndex])) - { - currentIndex++; - } - - tokens.Add(input.Substring(start, currentIndex - start)); - continue; - } + var token = tokens[pos++]; + if (int.TryParse(token, out var value)) + { + return new NumberNode(value); + } - throw new ArgumentException($"Invalid character '{currentChar}' at position {currentIndex}."); + if (token.Length != 1 || !"+-*/".Contains(token[0])) + { + throw new ArgumentException($"Invalid operator: {token}"); } - return tokens; + var op = token[0]; + var left = ParseExpression(tokens, ref pos); + var right = ParseExpression(tokens, ref pos); + + return new BinaryOperationNode(op, left, right); } - // - // Parse a list of tokens. - // - // The list of tokens. - // The current token index. - // The parsed node. - private static IAbstractNode ParseExpression(List tokens, ref int index) + private static bool AreParenthesesBalanced(string s) { - var operators = new Stack(); - var nodes = new Stack(); - - while (index < tokens.Count) + var balance = 0; + foreach (var c in s) { - var token = tokens[index]; - - if (int.TryParse(token, out var value)) - { - nodes.Push(new NumberNode(value)); - index++; - } - else if (token == "(") + if (c == '(') { - operators.Push(token); - index++; + balance++; } - else if ("+-*/".Contains(token)) - { - if (operators.Count == 0 || operators.Peek() != "(") - { - throw new ArgumentException($"Expected '(', found '{token}' at position {index}."); - } - operators.Push(token); - index++; - } - else if (token == ")") + if (c == ')') { - if (operators.Count < 2 || operators.Peek() == "(") - { - throw new ArgumentException($"Missing operation before ')' at position {index}."); - } - - var op = operators.Pop(); - if (operators.Peek() != "(") - { - throw new ArgumentException($"Missing opening '(' before position {index}."); - } - - operators.Pop(); - - if (nodes.Count < 2) - { - throw new ArgumentException($"Missing operands for operation '{op}' at position {index}."); - } - - var right = nodes.Pop(); - var left = nodes.Pop(); - nodes.Push(new BinaryOperationNode(op[0], left, right)); - index++; + balance--; } - else + + if (balance < 0) { - throw new ArgumentException($"Unexpected token '{token}' at position {index}."); + return false; } } - if (operators.Count > 0) - { - throw new ArgumentException("Missing closing ')'."); - } - - if (nodes.Count != 1) - { - throw new ArgumentException("Incomplete expression."); - } - - return nodes.Pop(); + return balance == 0; } } From 7d9bc95350bbf370e8b053171fe1321a698ad6ff Mon Sep 17 00:00:00 2001 From: khusainovilas Date: Tue, 2 Dec 2025 23:20:09 +0300 Subject: [PATCH 14/14] fix bug --- HW2/SyntaxTree.Tests/SyntaxTreeTests.cs | 7 +-- HW2/SyntaxTree/Add.cs | 27 ++++++++++++ HW2/SyntaxTree/BinaryOperationNode.cs | 57 ------------------------- HW2/SyntaxTree/Divide.cs | 36 ++++++++++++++++ HW2/SyntaxTree/Multiply.cs | 27 ++++++++++++ HW2/SyntaxTree/Operation.cs | 44 +++++++++++++++++++ HW2/SyntaxTree/Parser.cs | 9 +++- HW2/SyntaxTree/Subtract.cs | 27 ++++++++++++ 8 files changed, 173 insertions(+), 61 deletions(-) create mode 100644 HW2/SyntaxTree/Add.cs delete mode 100644 HW2/SyntaxTree/BinaryOperationNode.cs create mode 100644 HW2/SyntaxTree/Divide.cs create mode 100644 HW2/SyntaxTree/Multiply.cs create mode 100644 HW2/SyntaxTree/Operation.cs create mode 100644 HW2/SyntaxTree/Subtract.cs diff --git a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs index 3128b82..b92e73f 100644 --- a/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs +++ b/HW2/SyntaxTree.Tests/SyntaxTreeTests.cs @@ -109,12 +109,13 @@ public void NumberNode_CalculateAndToString_ReturnsCorrectValues() } /// - /// Test BinaryOperationNode calculation and string representation. + /// Test that Add node calculates and prints correctly. /// [Test] - public void BinaryOperationNode_CalculateAndToString_ReturnsCorrectValues() + public void Add_CalculateAndToString_ReturnsCorrectValues() { - var node = new BinaryOperationNode('+', new NumberNode(1), new NumberNode(2)); + var node = new Add(new NumberNode(1), new NumberNode(2)); + Assert.Multiple(() => { Assert.That(node.Calculate(), Is.EqualTo(3)); diff --git a/HW2/SyntaxTree/Add.cs b/HW2/SyntaxTree/Add.cs new file mode 100644 index 0000000..61fb0f2 --- /dev/null +++ b/HW2/SyntaxTree/Add.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Add operation. +/// +public class Add : Operation +{ + /// + /// Initializes a new instance of the class. + /// + /// Left operand of the operation. + /// Right operand of the operation. + public Add(IAbstractNode left, IAbstractNode right) + : base(left, right) + { + } + + /// + protected override char Symbol => '+'; + + /// + public override int Calculate() => this.Left.Calculate() + this.Right.Calculate(); +} \ No newline at end of file diff --git a/HW2/SyntaxTree/BinaryOperationNode.cs b/HW2/SyntaxTree/BinaryOperationNode.cs deleted file mode 100644 index cc6b775..0000000 --- a/HW2/SyntaxTree/BinaryOperationNode.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) khusainovilas. All rights reserved. -// - -namespace SyntaxTree; - -/// -/// Represents a binary operation node in the syntax tree. -/// -public class BinaryOperationNode : IAbstractNode -{ - private readonly char operation; - private readonly IAbstractNode leftNode; - private readonly IAbstractNode rightNode; - - /// - /// Initializes a new instance of the class. - /// - /// The operation character (+, -, *, /). - /// The left operand node. - /// The right operand node. - public BinaryOperationNode(char operation, IAbstractNode leftNode, IAbstractNode rightNode) - { - this.operation = operation; - this.leftNode = leftNode ?? throw new ArgumentNullException(nameof(leftNode)); - this.rightNode = rightNode ?? throw new ArgumentNullException(nameof(rightNode)); - this.Validate(); - } - - /// - public int Calculate() - { - var leftValue = this.leftNode.Calculate(); - var rightValue = this.rightNode.Calculate(); - - return this.operation switch - { - '+' => leftValue + rightValue, - '-' => leftValue - rightValue, - '*' => leftValue * rightValue, - '/' => rightValue != 0 ? leftValue / rightValue : throw new DivideByZeroException("Division by zero."), - _ => throw new InvalidOperationException("Unsupported operation."), - }; - } - - /// - public string ToStringRepresentation() => - $"({this.operation} {this.leftNode.ToStringRepresentation()} {this.rightNode.ToStringRepresentation()})"; - - private void Validate() - { - if (!"+-*/".Contains(this.operation)) - { - throw new ArgumentException("Invalid operator. Supported: +, -, *, /.", nameof(this.operation)); - } - } -} \ No newline at end of file diff --git a/HW2/SyntaxTree/Divide.cs b/HW2/SyntaxTree/Divide.cs new file mode 100644 index 0000000..41bb302 --- /dev/null +++ b/HW2/SyntaxTree/Divide.cs @@ -0,0 +1,36 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Divide operation. +/// +public class Divide : Operation +{ + /// + /// Initializes a new instance of the class. + /// + /// Left operand of the operation. + /// Right operand of the operation. + public Divide(IAbstractNode left, IAbstractNode right) + : base(left, right) + { + } + + /// + protected override char Symbol => '/'; + + /// + public override int Calculate() + { + var divisor = this.Right.Calculate(); + if (divisor == 0) + { + throw new DivideByZeroException("Division by zero."); + } + + return this.Left.Calculate() / divisor; + } +} \ No newline at end of file diff --git a/HW2/SyntaxTree/Multiply.cs b/HW2/SyntaxTree/Multiply.cs new file mode 100644 index 0000000..af84374 --- /dev/null +++ b/HW2/SyntaxTree/Multiply.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Multiply operation. +/// +public class Multiply : Operation +{ + /// + /// Initializes a new instance of the class. + /// + /// Left operand of the operation. + /// Right operand of the operation. + public Multiply(IAbstractNode left, IAbstractNode right) + : base(left, right) + { + } + + /// + protected override char Symbol => '*'; + + /// + public override int Calculate() => this.Left.Calculate() * this.Right.Calculate(); +} \ No newline at end of file diff --git a/HW2/SyntaxTree/Operation.cs b/HW2/SyntaxTree/Operation.cs new file mode 100644 index 0000000..efae6d9 --- /dev/null +++ b/HW2/SyntaxTree/Operation.cs @@ -0,0 +1,44 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Base class for all binary operations. +/// +public abstract class Operation : IAbstractNode +{ + /// + /// Initializes a new instance of the class. + /// + /// Left operand of the operation. + /// Right operand of the operation. + protected Operation(IAbstractNode left, IAbstractNode right) + { + this.Left = left; + this.Right = right; + } + + /// + /// Gets left child of operator. + /// + protected IAbstractNode Left { get; } + + /// + /// Gets right child of operator. + /// + protected IAbstractNode Right { get; } + + /// + /// Gets symbol of operation. + /// + protected abstract char Symbol { get; } + + /// + public abstract int Calculate(); + + /// + public string ToStringRepresentation() + => $"({this.Symbol} {this.Left.ToStringRepresentation()} {this.Right.ToStringRepresentation()})"; +} \ No newline at end of file diff --git a/HW2/SyntaxTree/Parser.cs b/HW2/SyntaxTree/Parser.cs index 60a527a..075b079 100644 --- a/HW2/SyntaxTree/Parser.cs +++ b/HW2/SyntaxTree/Parser.cs @@ -64,7 +64,14 @@ private static IAbstractNode ParseExpression(string[] tokens, ref int pos) var left = ParseExpression(tokens, ref pos); var right = ParseExpression(tokens, ref pos); - return new BinaryOperationNode(op, left, right); + return op switch + { + '+' => new Add(left, right), + '-' => new Subtract(left, right), + '*' => new Multiply(left, right), + '/' => new Divide(left, right), + _ => throw new ArgumentException($"Unknown operator: {op}"), + }; } private static bool AreParenthesesBalanced(string s) diff --git a/HW2/SyntaxTree/Subtract.cs b/HW2/SyntaxTree/Subtract.cs new file mode 100644 index 0000000..4c01f0c --- /dev/null +++ b/HW2/SyntaxTree/Subtract.cs @@ -0,0 +1,27 @@ +// +// Copyright (c) khusainovilas. All rights reserved. +// + +namespace SyntaxTree; + +/// +/// Subtract operation. +/// +public class Subtract : Operation +{ + /// + /// Initializes a new instance of the class. + /// + /// Left operand of the operation. + /// Right operand of the operation. + public Subtract(IAbstractNode left, IAbstractNode right) + : base(left, right) + { + } + + /// + protected override char Symbol => '-'; + + /// + public override int Calculate() => this.Left.Calculate() - this.Right.Calculate(); +} \ No newline at end of file