From bdcc59a2ab73e261b288d3fbd187c7a35f67374d Mon Sep 17 00:00:00 2001 From: jackfiled Date: Thu, 15 Aug 2024 16:18:32 +0800 Subject: [PATCH] add: basic grammar parser including expression and program. --- CanonSharp.Pascal/Parser/GrammarParser.cs | 201 ++++++++++++++ CanonSharp.Pascal/Parser/GrammarParserBase.cs | 17 ++ .../Parser/LexicalTokenReadState.cs | 53 ++++ CanonSharp.Pascal/SyntaxTree/AssignNode.cs | 10 + .../SyntaxTree/BinaryOperatorNode.cs | 33 +++ CanonSharp.Pascal/SyntaxTree/BlockNode.cs | 8 + .../SyntaxTree/BooleanValueNode.cs | 8 + .../SyntaxTree/FloatValueNode.cs | 8 + .../SyntaxTree/IntegerValueNode.cs | 8 + CanonSharp.Pascal/SyntaxTree/Program.cs | 10 + CanonSharp.Pascal/SyntaxTree/ProgramBody.cs | 8 + CanonSharp.Pascal/SyntaxTree/ProgramHead.cs | 10 + .../SyntaxTree/SyntaxNodeBase.cs | 31 +++ .../SyntaxTree/UnaryOperatorNode.cs | 17 ++ CanonSharp.Pascal/SyntaxTree/VariableNode.cs | 8 + .../ParserTests/ExpressionParserTests.cs | 245 ++++++++++++++++++ .../ParserTests/ProgramParserTests.cs | 41 +++ .../ParserTests/StatementParserTests.cs | 82 ++++++ .../Utils/GrammarParserTestBase.cs | 31 +++ 19 files changed, 829 insertions(+) create mode 100644 CanonSharp.Pascal/Parser/GrammarParser.cs create mode 100644 CanonSharp.Pascal/Parser/GrammarParserBase.cs create mode 100644 CanonSharp.Pascal/Parser/LexicalTokenReadState.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/AssignNode.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/BinaryOperatorNode.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/BlockNode.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/BooleanValueNode.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/FloatValueNode.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/IntegerValueNode.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/Program.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/ProgramBody.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/ProgramHead.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/UnaryOperatorNode.cs create mode 100644 CanonSharp.Pascal/SyntaxTree/VariableNode.cs create mode 100644 CanonSharp.Tests/ParserTests/ExpressionParserTests.cs create mode 100644 CanonSharp.Tests/ParserTests/ProgramParserTests.cs create mode 100644 CanonSharp.Tests/ParserTests/StatementParserTests.cs create mode 100644 CanonSharp.Tests/Utils/GrammarParserTestBase.cs diff --git a/CanonSharp.Pascal/Parser/GrammarParser.cs b/CanonSharp.Pascal/Parser/GrammarParser.cs new file mode 100644 index 0000000..8dc292b --- /dev/null +++ b/CanonSharp.Pascal/Parser/GrammarParser.cs @@ -0,0 +1,201 @@ +using CanonSharp.Combinator.Abstractions; +using CanonSharp.Combinator.Extensions; +using static CanonSharp.Combinator.ParserBuilder; +using CanonSharp.Pascal.Scanner; +using CanonSharp.Pascal.SyntaxTree; + +namespace CanonSharp.Pascal.Parser; + +public sealed class GrammarParser : GrammarParserBuilder +{ + public static IParser FactorParser() + { + // factor -> true | false | num + IParser trueParser = from _ in Keyword("true") + select new BooleanValueNode(true); + + IParser falseParser = from _ in Keyword("false") + select new BooleanValueNode(false); + + IParser integerParser = + from token in Satisfy(x => x.TokenType == LexicalTokenType.ConstInteger) + select new IntegerValueNode(int.Parse(token.LiteralValue)); + + IParser floatParser = + from token in Satisfy(x => x.TokenType == LexicalTokenType.ConstFloat) + select new FloatValueNode(double.Parse(token.LiteralValue)); + + // factor -> - factor | + factor + IParser minusParser = + from _ in Operator("-") + from node in FactorParser() + select new UnaryOperatorNode(UnaryOperatorType.Minus, node); + + IParser plusParser = + from _ in Operator("+") + from node in FactorParser() + select new UnaryOperatorNode(UnaryOperatorType.Plus, node); + + IParser notParser = + from _ in Keyword("not") + from node in FactorParser() + select new UnaryOperatorNode(UnaryOperatorType.Not, node); + + IParser parenthesisParser = + from _1 in Delimiter("(") + from node in ExpressionParser() + from _2 in Delimiter(")") + select node; + + return Choice( + trueParser, + falseParser, + integerParser, + floatParser, + minusParser, + plusParser, + notParser, + VariableParser(), + parenthesisParser + ); + } + + private static IParser TermRecursively(SyntaxNodeBase left) + { + // MultiplyOperator -> * | / | div | mod | and + IParser multiplyOperator = Choice( + from _ in Operator("*") + select BinaryOperatorType.Multiply, + from _ in Operator("/") + select BinaryOperatorType.Divide, + from _ in Keyword("div") + select BinaryOperatorType.IntegerDivide, + from _ in Keyword("mod") + select BinaryOperatorType.Mod, + from _ in Keyword("and") + select BinaryOperatorType.And); + + return multiplyOperator.Next(op => + { + return FactorParser().Map(right => new BinaryOperatorNode(op, left, right)) + .Bind(TermRecursively); + }, left); + } + + public static IParser TermParser() + { + // Term -> Factor | Term MultiplyOperator Factor + // 消除左递归为 + // Term -> Factor Term' + // Term' -> MultiplyOperator Factor Term' | ε + return FactorParser().Bind(TermRecursively); + } + + private static IParser SimpleExpressionRecursively(SyntaxNodeBase left) + { + // AddOperator -> + | - | or + IParser addOperator = Choice( + from _ in Operator("+") + select BinaryOperatorType.Add, + from _ in Operator("-") + select BinaryOperatorType.Subtract, + from _ in Keyword("or") + select BinaryOperatorType.Or); + + return addOperator.Next(op => + { + return TermParser().Map(right => new BinaryOperatorNode(op, left, right)) + .Bind(SimpleExpressionRecursively); + }, left); + } + + public static IParser SimpleExpressionParser() + { + // SimpleExpression -> Term | SimpleExpression AddOperator Term + // 消除左递归为 + // SimpleExpression -> Term SimpleExpression' + // SimpleExpression' -> AddOperator Term SimpleExpression' | ε + return TermParser().Bind(SimpleExpressionRecursively); + } + + public static IParser ExpressionParser() + { + // RelationOperator -> = | <> | < | <= | > | >= + IParser relationOperator = Choice( + from _ in Operator("=") + select BinaryOperatorType.Equal, + from _ in Operator("<>") + select BinaryOperatorType.NotEqual, + from _ in Operator("<") + select BinaryOperatorType.Less, + from _ in Operator("<=") + select BinaryOperatorType.LessEqual, + from _ in Operator(">") + select BinaryOperatorType.Greater, + from _ in Operator(">=") + select BinaryOperatorType.GreaterEqual); + + // Expression -> SimpleExpression | SimpleExpression RelationOperator SimpleExpression + return Choice( + from left in SimpleExpressionParser() + from op in relationOperator + from right in SimpleExpressionParser() + select new BinaryOperatorNode(op, left, right), + SimpleExpressionParser() + ); + } + + /// + /// ExpressionList Parser + /// ExpressionList -> Expression | ExpressionList , Expression + /// + /// + public static IParser> ExpressionsParser() + => ExpressionParser().SeparatedBy1(Delimiter(",")); + + public static IParser VariableParser() + { + return from token in Satisfy(token => token.TokenType == LexicalTokenType.Identifier) + select new VariableNode(token.LiteralValue); + } + + public static IParser StatementParser() + { + return Choice( + from variable in VariableParser() + from _ in Operator(":=") + from expression in ExpressionParser() + select new AssignNode(variable, expression) + ); + } + + public static IParser CompoundStatementParser() + { + return from _1 in Keyword("begin") + from statements in StatementParser().SeparatedOrEndBy(Delimiter(";")) + from _2 in Keyword("end") + select new BlockNode(statements); + } + + public static IParser ProgramBodyParser() + { + return from block in CompoundStatementParser() + select new ProgramBody(block); + } + + public static IParser ProgramHeadParser() + { + return from _ in Keyword("program") + from token in Satisfy(token => token.TokenType == LexicalTokenType.Identifier) + select new ProgramHead(token); + } + + public static IParser ProgramParser() + { + return from head in ProgramHeadParser() + from _1 in Delimiter(";") + from body in ProgramBodyParser() + from _2 in Delimiter(".") + select new Program(head, body); + } +} diff --git a/CanonSharp.Pascal/Parser/GrammarParserBase.cs b/CanonSharp.Pascal/Parser/GrammarParserBase.cs new file mode 100644 index 0000000..e4affe6 --- /dev/null +++ b/CanonSharp.Pascal/Parser/GrammarParserBase.cs @@ -0,0 +1,17 @@ +using CanonSharp.Combinator.Abstractions; +using CanonSharp.Pascal.Scanner; +using static CanonSharp.Combinator.ParserBuilder; + +namespace CanonSharp.Pascal.Parser; + +public abstract class GrammarParserBuilder +{ + protected static IParser Keyword(string value) + => Satisfy(token => token.TokenType == LexicalTokenType.Keyword && token.LiteralValue == value); + + protected static IParser Operator(string value) + => Satisfy(token => token.TokenType == LexicalTokenType.Operator && token.LiteralValue == value); + + protected static IParser Delimiter(string value) + => Satisfy(token => token.TokenType == LexicalTokenType.Delimiter && token.LiteralValue == value); +} diff --git a/CanonSharp.Pascal/Parser/LexicalTokenReadState.cs b/CanonSharp.Pascal/Parser/LexicalTokenReadState.cs new file mode 100644 index 0000000..85be52e --- /dev/null +++ b/CanonSharp.Pascal/Parser/LexicalTokenReadState.cs @@ -0,0 +1,53 @@ +using CanonSharp.Combinator.Abstractions; +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.Parser; + +public sealed class LexicalTokenReadState : IReadState +{ + private readonly List _tokens; + private readonly int _pos; + + public LexicalToken Current => _tokens[_pos]; + + public bool HasValue => _pos < _tokens.Count; + + public LexicalTokenReadState Next => new(_tokens, _pos + 1); + + private LexicalTokenReadState(List tokens, int pos) + { + _tokens = tokens; + _pos = pos; + } + + public LexicalTokenReadState(IEnumerable tokens) + { + _tokens = tokens.ToList(); + _pos = 0; + } + + public bool Equals(LexicalTokenReadState? other) + { + if (other is null) + { + return false; + } + + if (_tokens.Count != other._tokens.Count) + { + return false; + } + + foreach ((LexicalToken first, LexicalToken second) in _tokens.Zip(other._tokens)) + { + if (!first.Equals(second)) + { + return false; + } + } + + return _pos == other._pos; + } + + public override string ToString() => HasValue ? Current.ToString() : "End of input stream."; +} diff --git a/CanonSharp.Pascal/SyntaxTree/AssignNode.cs b/CanonSharp.Pascal/SyntaxTree/AssignNode.cs new file mode 100644 index 0000000..87f8958 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/AssignNode.cs @@ -0,0 +1,10 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class AssignNode(VariableNode variable, SyntaxNodeBase expression) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.Assign; + + public VariableNode Variable => variable; + + public SyntaxNodeBase Expression => expression; +} diff --git a/CanonSharp.Pascal/SyntaxTree/BinaryOperatorNode.cs b/CanonSharp.Pascal/SyntaxTree/BinaryOperatorNode.cs new file mode 100644 index 0000000..6e95b56 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/BinaryOperatorNode.cs @@ -0,0 +1,33 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public enum BinaryOperatorType +{ + Add, + Subtract, + Multiply, + + // Pascal特有的整数除法关键词div + IntegerDivide, + Divide, + Mod, + And, + Or, + Equal, + NotEqual, + Greater, + GreaterEqual, + Less, + LessEqual +} + +public sealed class BinaryOperatorNode(BinaryOperatorType operatorType, SyntaxNodeBase left, SyntaxNodeBase right) + : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.BinaryOperator; + + public BinaryOperatorType OperatorType => operatorType; + + public SyntaxNodeBase Left => left; + + public SyntaxNodeBase Right => right; +} diff --git a/CanonSharp.Pascal/SyntaxTree/BlockNode.cs b/CanonSharp.Pascal/SyntaxTree/BlockNode.cs new file mode 100644 index 0000000..6359271 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/BlockNode.cs @@ -0,0 +1,8 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class BlockNode(IEnumerable statements) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.Block; + + public IList Statements => statements.ToList(); +} diff --git a/CanonSharp.Pascal/SyntaxTree/BooleanValueNode.cs b/CanonSharp.Pascal/SyntaxTree/BooleanValueNode.cs new file mode 100644 index 0000000..d259abf --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/BooleanValueNode.cs @@ -0,0 +1,8 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class BooleanValueNode(bool value) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.BooleanValue; + + public bool Value => value; +} diff --git a/CanonSharp.Pascal/SyntaxTree/FloatValueNode.cs b/CanonSharp.Pascal/SyntaxTree/FloatValueNode.cs new file mode 100644 index 0000000..215c006 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/FloatValueNode.cs @@ -0,0 +1,8 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class FloatValueNode(double value) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.FloatValue; + + public double Value => value; +} diff --git a/CanonSharp.Pascal/SyntaxTree/IntegerValueNode.cs b/CanonSharp.Pascal/SyntaxTree/IntegerValueNode.cs new file mode 100644 index 0000000..0d34243 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/IntegerValueNode.cs @@ -0,0 +1,8 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class IntegerValueNode(int value) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.IntegerValue; + + public int Value => value; +} diff --git a/CanonSharp.Pascal/SyntaxTree/Program.cs b/CanonSharp.Pascal/SyntaxTree/Program.cs new file mode 100644 index 0000000..fa773b1 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/Program.cs @@ -0,0 +1,10 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class Program(ProgramHead head, ProgramBody body) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.Program; + + public ProgramHead Head => head; + + public ProgramBody Body => body; +} diff --git a/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs b/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs new file mode 100644 index 0000000..0ca95d6 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs @@ -0,0 +1,8 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class ProgramBody(BlockNode block) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.ProgramBody; + + public BlockNode MainBlock => block; +} diff --git a/CanonSharp.Pascal/SyntaxTree/ProgramHead.cs b/CanonSharp.Pascal/SyntaxTree/ProgramHead.cs new file mode 100644 index 0000000..a20ce24 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/ProgramHead.cs @@ -0,0 +1,10 @@ +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class ProgramHead(LexicalToken programName) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.ProgramHead; + + public LexicalToken ProgramName => programName; +} diff --git a/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs b/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs new file mode 100644 index 0000000..a8675e4 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs @@ -0,0 +1,31 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public enum SyntaxNodeType +{ + BinaryOperator, + UnaryOperator, + IntegerValue, + FloatValue, + BooleanValue, + Variable, + Assign, + Block, + ProgramBody, + ProgramHead, + Program +} + +public abstract class SyntaxNodeBase +{ + public abstract SyntaxNodeType NodeType { get; } + + public T Convert() where T : SyntaxNodeBase + { + if (this is not T result) + { + throw new InvalidCastException($"Can't convert {NodeType} to target node."); + } + + return result; + } +} diff --git a/CanonSharp.Pascal/SyntaxTree/UnaryOperatorNode.cs b/CanonSharp.Pascal/SyntaxTree/UnaryOperatorNode.cs new file mode 100644 index 0000000..2a5fb4b --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/UnaryOperatorNode.cs @@ -0,0 +1,17 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public enum UnaryOperatorType +{ + Plus, + Minus, + Not +} + +public sealed class UnaryOperatorNode(UnaryOperatorType operatorType, SyntaxNodeBase node) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.UnaryOperator; + + public UnaryOperatorType OperatorType => operatorType; + + public SyntaxNodeBase Node => node; +} diff --git a/CanonSharp.Pascal/SyntaxTree/VariableNode.cs b/CanonSharp.Pascal/SyntaxTree/VariableNode.cs new file mode 100644 index 0000000..e14d2cf --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/VariableNode.cs @@ -0,0 +1,8 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class VariableNode(string name) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.Variable; + + public string IdentifierName => name; +} diff --git a/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs b/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs new file mode 100644 index 0000000..aaa82fb --- /dev/null +++ b/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs @@ -0,0 +1,245 @@ +using CanonSharp.Pascal.Parser; +using CanonSharp.Pascal.SyntaxTree; +using CanonSharp.Tests.Utils; + +namespace CanonSharp.Tests.ParserTests; + +public sealed class ExpressionParserTests : GrammarParserTestBase +{ + [Fact] + public void BoolTest() + { + BooleanValueNode node = RunParser(GrammarParser.FactorParser(), "false"); + Assert.False(node.Value); + + node = RunParser(GrammarParser.FactorParser(), "true"); + Assert.True(node.Value); + } + + [Fact] + public void NumberTest() + { + IntegerValueNode integerValueNode = RunParser(GrammarParser.FactorParser(), "123456"); + Assert.Equal(123456, integerValueNode.Value); + + FloatValueNode floatValueNode = RunParser(GrammarParser.FactorParser(), "123.456"); + Assert.Equal(123.456, floatValueNode.Value); + } + + [Fact] + public void UnaryOperatorTest() + { + UnaryOperatorNode node = RunParser(GrammarParser.FactorParser(), "- 123"); + Assert.Equal(UnaryOperatorType.Minus, node.OperatorType); + Assert.Equal(123, node.Node.Convert().Value); + + node = RunParser(GrammarParser.FactorParser(), "+ 100.5"); + Assert.Equal(UnaryOperatorType.Plus, node.OperatorType); + Assert.Equal(100.5, node.Node.Convert().Value); + } + + [Fact] + public void IdentifierTest() + { + VariableNode node = RunParser(GrammarParser.FactorParser(), "temp"); + Assert.Equal("temp", node.IdentifierName); + } + + [Fact] + public void SingleTermTest() + { + VariableNode node = RunParser(GrammarParser.TermParser(), "temp"); + Assert.Equal("temp", node.IdentifierName); + + UnaryOperatorNode unaryOperatorNode = RunParser(GrammarParser.TermParser(), "- 123"); + Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType); + Assert.Equal(123, unaryOperatorNode.Node.Convert().Value); + + unaryOperatorNode = RunParser(GrammarParser.TermParser(), "+ 100.5"); + Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType); + Assert.Equal(100.5, unaryOperatorNode.Node.Convert().Value); + } + + [Fact] + public void MultiplyTermTest1() + { + BinaryOperatorNode node = RunParser(GrammarParser.TermParser(), "10 / 2"); + + Assert.Equal(BinaryOperatorType.Divide, node.OperatorType); + Assert.Equal(10, node.Left.Convert().Value); + Assert.Equal(2, node.Right.Convert().Value); + } + + [Fact] + public void MultiplyTermTest2() + { + BinaryOperatorNode node = RunParser(GrammarParser.TermParser(), "10 div 2"); + + Assert.Equal(BinaryOperatorType.IntegerDivide, node.OperatorType); + Assert.Equal(10, node.Left.Convert().Value); + Assert.Equal(2, node.Right.Convert().Value); + } + + [Fact] + public void MultiplyTermTest3() + { + BinaryOperatorNode node = RunParser(GrammarParser.TermParser(), "temp * 2 div 3"); + + Assert.Equal(BinaryOperatorType.IntegerDivide, node.OperatorType); + Assert.Equal(3, node.Right.Convert().Value); + BinaryOperatorNode leftNode = node.Left.Convert(); + Assert.Equal(BinaryOperatorType.Multiply, leftNode.OperatorType); + Assert.Equal("temp", leftNode.Left.Convert().IdentifierName); + Assert.Equal(2, leftNode.Right.Convert().Value); + } + + [Fact] + public void SimpleExpressionTest1() + { + VariableNode node = RunParser(GrammarParser.SimpleExpressionParser(), "temp"); + Assert.Equal("temp", node.IdentifierName); + + UnaryOperatorNode unaryOperatorNode = + RunParser(GrammarParser.SimpleExpressionParser(), "- 123"); + Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType); + Assert.Equal(123, unaryOperatorNode.Node.Convert().Value); + + unaryOperatorNode = RunParser(GrammarParser.SimpleExpressionParser(), "+ 100.5"); + Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType); + Assert.Equal(100.5, unaryOperatorNode.Node.Convert().Value); + } + + [Fact] + public void SimpleExpressionTest2() + { + BinaryOperatorNode node = RunParser(GrammarParser.SimpleExpressionParser(), "1 + 1"); + + Assert.Equal(BinaryOperatorType.Add, node.OperatorType); + Assert.Equal(1, node.Left.Convert().Value); + Assert.Equal(1, node.Right.Convert().Value); + + node = RunParser(GrammarParser.SimpleExpressionParser(), "1 + 1 - 2"); + + Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType); + Assert.Equal(2, node.Right.Convert().Value); + + BinaryOperatorNode leftNode = node.Left.Convert(); + Assert.Equal(BinaryOperatorType.Add, leftNode.OperatorType); + Assert.Equal(1, leftNode.Left.Convert().Value); + Assert.Equal(1, leftNode.Right.Convert().Value); + } + + [Fact] + public void SimpleExpressionTest3() + { + BinaryOperatorNode node = RunParser(GrammarParser.SimpleExpressionParser(), "1 - 2 * 5"); + + Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType); + Assert.Equal(1, node.Left.Convert().Value); + + BinaryOperatorNode rightNode = node.Right.Convert(); + Assert.Equal(BinaryOperatorType.Multiply, rightNode.OperatorType); + Assert.Equal(2, rightNode.Left.Convert().Value); + Assert.Equal(5, rightNode.Right.Convert().Value); + } + + [Fact] + public void SimpleExpressionTest4() + { + BinaryOperatorNode node = RunParser(GrammarParser.SimpleExpressionParser(), "1 + +1"); + + Assert.Equal(BinaryOperatorType.Add, node.OperatorType); + Assert.Equal(1, node.Left.Convert().Value); + UnaryOperatorNode unaryOperatorNode = node.Right.Convert(); + Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType); + Assert.Equal(1, unaryOperatorNode.Node.Convert().Value); + + node = RunParser(GrammarParser.SimpleExpressionParser(), "1 - -1"); + + Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType); + Assert.Equal(1, node.Left.Convert().Value); + unaryOperatorNode = node.Right.Convert(); + Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType); + Assert.Equal(1, unaryOperatorNode.Node.Convert().Value); + + node = RunParser(GrammarParser.SimpleExpressionParser(), "+ 1 - - 1"); + + Assert.Equal(BinaryOperatorType.Subtract, node.OperatorType); + unaryOperatorNode = node.Left.Convert(); + Assert.Equal(UnaryOperatorType.Plus, unaryOperatorNode.OperatorType); + Assert.Equal(1, unaryOperatorNode.Node.Convert().Value); + unaryOperatorNode = node.Right.Convert(); + Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType); + Assert.Equal(1, unaryOperatorNode.Node.Convert().Value); + } + + [Fact] + public void SimpleExpressionTest5() + { + BinaryOperatorNode node = RunParser(GrammarParser.SimpleExpressionParser(), + "true and temp or temp and false"); + + Assert.Equal(BinaryOperatorType.Or, node.OperatorType); + + BinaryOperatorNode left = node.Left.Convert(); + Assert.Equal(BinaryOperatorType.And, left.OperatorType); + BinaryOperatorNode right = node.Right.Convert(); + Assert.Equal(BinaryOperatorType.And, right.OperatorType); + } + + [Fact] + public void ExpressionTest1() + { + BinaryOperatorNode node = RunParser(GrammarParser.ExpressionParser(), + "true and temp or temp and false"); + + Assert.Equal(BinaryOperatorType.Or, node.OperatorType); + + BinaryOperatorNode left = node.Left.Convert(); + Assert.Equal(BinaryOperatorType.And, left.OperatorType); + BinaryOperatorNode right = node.Right.Convert(); + Assert.Equal(BinaryOperatorType.And, right.OperatorType); + } + + [Fact] + public void ExpressionTest2() + { + BinaryOperatorNode node = RunParser(GrammarParser.ExpressionParser(), "2 >= 1"); + Assert.Equal(BinaryOperatorType.GreaterEqual, node.OperatorType); + Assert.Equal(2, node.Left.Convert().Value); + Assert.Equal(1, node.Right.Convert().Value); + } + + [Fact] + public void ExpressionTest3() + { + BinaryOperatorNode node = RunParser(GrammarParser.ExpressionParser(), "(1 + 1) * 2"); + + Assert.Equal(BinaryOperatorType.Multiply, node.OperatorType); + Assert.Equal(2, node.Right.Convert().Value); + + node = node.Left.Convert(); + Assert.Equal(BinaryOperatorType.Add, node.OperatorType); + Assert.Equal(1, node.Left.Convert().Value); + Assert.Equal(1, node.Right.Convert().Value); + } + + [Fact] + public void ExpressionsTest1() + { + List nodes = + RunParser(GrammarParser.ExpressionsParser(), "1 + 1, 2 * 3"); + + Assert.Equal(BinaryOperatorType.Add, nodes[0].OperatorType); + Assert.Equal(BinaryOperatorType.Multiply, nodes[1].OperatorType); + } + + [Fact] + public void VariableTest1() + { + VariableNode node = RunParser(GrammarParser.VariableParser(), "temp"); + Assert.Equal("temp", node.IdentifierName); + } + + +} diff --git a/CanonSharp.Tests/ParserTests/ProgramParserTests.cs b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs new file mode 100644 index 0000000..fddf419 --- /dev/null +++ b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs @@ -0,0 +1,41 @@ +using CanonSharp.Pascal.SyntaxTree; +using CanonSharp.Tests.Utils; + +namespace CanonSharp.Tests.ParserTests; + +public class ProgramParserTests : GrammarParserTestBase +{ + [Fact] + public void ProgramParseTest1() + { + Program program = ProgramParse(""" + program main; + begin + end. + """); + + Assert.Equal("main", program.Head.ProgramName.LiteralValue); + Assert.Empty(program.Body.MainBlock.Statements); + } + + [Fact] + public void ProgramParseTest2() + { + Program program = ProgramParse(""" + program main; + begin + temp := 1 + 1; + end. + """); + + Assert.Equal("main", program.Head.ProgramName.LiteralValue); + + AssignNode assignNode = program.Body.MainBlock.Statements[0].Convert(); + Assert.Equal("temp", assignNode.Variable.IdentifierName); + + BinaryOperatorNode binaryOperatorNode = assignNode.Expression.Convert(); + Assert.Equal(BinaryOperatorType.Add, binaryOperatorNode.OperatorType); + Assert.Equal(1, binaryOperatorNode.Left.Convert().Value); + Assert.Equal(1, binaryOperatorNode.Right.Convert().Value); + } +} diff --git a/CanonSharp.Tests/ParserTests/StatementParserTests.cs b/CanonSharp.Tests/ParserTests/StatementParserTests.cs new file mode 100644 index 0000000..ce2d7e5 --- /dev/null +++ b/CanonSharp.Tests/ParserTests/StatementParserTests.cs @@ -0,0 +1,82 @@ +using CanonSharp.Pascal.Parser; +using CanonSharp.Pascal.SyntaxTree; +using CanonSharp.Tests.Utils; + +namespace CanonSharp.Tests.ParserTests; + +public class StatementParserTests : GrammarParserTestBase +{ + [Fact] + public void StatementTest1() + { + AssignNode node = RunParser(GrammarParser.StatementParser(), "temp := 1"); + + Assert.Equal("temp", node.Variable.IdentifierName); + Assert.Equal(1, node.Expression.Convert().Value); + } + + [Theory] + [InlineData(""" + begin + temp := 1 + 1; + flag := true and false; + end + """)] + [InlineData(""" + begin + temp := 1 + 1; + flag := true and false + end + """)] + public void CompoundStatementTest1(string input) + { + BlockNode node = RunParser(GrammarParser.CompoundStatementParser(), input); + + Assert.Equal(2, node.Statements.Count); + AssignNode assignNode = node.Statements[0].Convert(); + Assert.Equal("temp", assignNode.Variable.IdentifierName); + + assignNode = node.Statements[1].Convert(); + Assert.Equal("flag", assignNode.Variable.IdentifierName); + } + + [Fact] + public void CompoundStatementTest2() + { + BlockNode node = RunParser(GrammarParser.CompoundStatementParser(), "begin end"); + Assert.Empty(node.Statements); + } + + [Fact] + public void ProgramHeadTest1() + { + ProgramHead head = RunParser(GrammarParser.ProgramHeadParser(), "program main"); + + Assert.Equal("main", head.ProgramName.LiteralValue); + } + + [Theory] + [InlineData(""" + begin + temp := 1 + 1; + flag := true and false; + end + """)] + [InlineData(""" + begin + temp := 1 + 1; + flag := true and false + end + """)] + public void ProgramBodyTest1(string input) + { + BlockNode node = RunParser(GrammarParser.ProgramBodyParser(), input).MainBlock; + + Assert.Equal(2, node.Statements.Count); + AssignNode assignNode = node.Statements[0].Convert(); + Assert.Equal("temp", assignNode.Variable.IdentifierName); + + assignNode = node.Statements[1].Convert(); + Assert.Equal("flag", assignNode.Variable.IdentifierName); + } +} diff --git a/CanonSharp.Tests/Utils/GrammarParserTestBase.cs b/CanonSharp.Tests/Utils/GrammarParserTestBase.cs new file mode 100644 index 0000000..dbba9b9 --- /dev/null +++ b/CanonSharp.Tests/Utils/GrammarParserTestBase.cs @@ -0,0 +1,31 @@ +using CanonSharp.Combinator.Abstractions; +using CanonSharp.Pascal.Parser; +using CanonSharp.Pascal.Scanner; +using CanonSharp.Pascal.SyntaxTree; + +namespace CanonSharp.Tests.Utils; + +public abstract class GrammarParserTestBase +{ + protected static T RunParser(IParser parser, string input) where T : SyntaxNodeBase + { + LexicalScanner scanner = new(); + LexicalTokenReadState state = new(scanner.Tokenize(new StringReadState(input))); + IParseResult parseResult = parser.Parse(state); + + return parseResult.Value.Convert(); + } + + protected static List RunParser(IParser> parser, + string input) where T : SyntaxNodeBase + { + LexicalScanner scanner = new(); + LexicalTokenReadState state = new(scanner.Tokenize(new StringReadState(input))); + IParseResult> parseResult = parser.Parse(state); + + return parseResult.Value.Select(node => node.Convert()).ToList(); + } + + protected static Program ProgramParse(string input) + => RunParser(GrammarParser.ProgramParser(), input); +}