diff --git a/CanonSharp.Pascal/Parser/GrammarParser.cs b/CanonSharp.Pascal/Parser/GrammarParser.cs index 8dc292b..f99da8e 100644 --- a/CanonSharp.Pascal/Parser/GrammarParser.cs +++ b/CanonSharp.Pascal/Parser/GrammarParser.cs @@ -10,21 +10,6 @@ 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("-") @@ -48,10 +33,9 @@ public sealed class GrammarParser : GrammarParserBuilder select node; return Choice( - trueParser, - falseParser, - integerParser, - floatParser, + TrueParser(), + FalseParser(), + NumberParser(), minusParser, plusParser, notParser, @@ -166,7 +150,7 @@ public sealed class GrammarParser : GrammarParserBuilder from _ in Operator(":=") from expression in ExpressionParser() select new AssignNode(variable, expression) - ); + ); } public static IParser CompoundStatementParser() @@ -177,10 +161,66 @@ public sealed class GrammarParser : GrammarParserBuilder select new BlockNode(statements); } + public static IParser ConstValueParser() + { + return Choice( + from _ in Operator("-") + from num in NumberParser() + select new UnaryOperatorNode(UnaryOperatorType.Minus, num), + from _ in Operator("+") + from num in NumberParser() + select new UnaryOperatorNode(UnaryOperatorType.Plus, num), + NumberParser(), + CharParser(), + TrueParser(), + FalseParser() + ); + } + + public static IParser ConstDeclarationParser() + { + return from identifier in Satisfy(token => + token.TokenType == LexicalTokenType.Identifier) + from _ in Operator("=") + from node in ConstValueParser() + select new ConstantNode(identifier, node); + } + + public static IParser ConstDeclarationsParser() + { + return (from _ in Keyword("const") + from tokens in ConstDeclarationParser().SeparatedOrEndBy1(Delimiter(";")) + select new BlockNode(tokens)).Try(new BlockNode([])); + } + + public static IParser TypeParser() + { + return from token in BasicTypeParser() + select new TypeNode(token); + } + + public static IParser VariableDeclarationParser() + { + return from tokens in Satisfy( + token => token.TokenType == LexicalTokenType.Identifier).SeparatedBy1(Delimiter(",")) + from _1 in Delimiter(":") + from type in TypeParser() + select new VariableDeclarationNode(tokens, type.Convert()); + } + + public static IParser VariableDeclarationsParser() + { + return (from _ in Keyword("var") + from nodes in VariableDeclarationParser().SeparatedOrEndBy1(Delimiter(";")) + select new BlockNode(nodes)).Try(new BlockNode([])); + } + public static IParser ProgramBodyParser() { - return from block in CompoundStatementParser() - select new ProgramBody(block); + return from constant in ConstDeclarationsParser() + from variables in VariableDeclarationsParser() + from block in CompoundStatementParser() + select new ProgramBody(constant.Convert(), variables, block); } public static IParser ProgramHeadParser() diff --git a/CanonSharp.Pascal/Parser/GrammarParserBase.cs b/CanonSharp.Pascal/Parser/GrammarParserBase.cs index e4affe6..28404fa 100644 --- a/CanonSharp.Pascal/Parser/GrammarParserBase.cs +++ b/CanonSharp.Pascal/Parser/GrammarParserBase.cs @@ -1,5 +1,7 @@ using CanonSharp.Combinator.Abstractions; +using CanonSharp.Combinator.Extensions; using CanonSharp.Pascal.Scanner; +using CanonSharp.Pascal.SyntaxTree; using static CanonSharp.Combinator.ParserBuilder; namespace CanonSharp.Pascal.Parser; @@ -14,4 +16,42 @@ public abstract class GrammarParserBuilder protected static IParser Delimiter(string value) => Satisfy(token => token.TokenType == LexicalTokenType.Delimiter && token.LiteralValue == value); + + protected static IParser TrueParser() + { + return from _ in Keyword("true") + select new BooleanValueNode(true); + } + + protected static IParser FalseParser() + { + return from _ in Keyword("false") + select new BooleanValueNode(false); + } + + protected static IParser NumberParser() + { + return Choice( + from token in Satisfy(x => x.TokenType == LexicalTokenType.ConstInteger) + select new IntegerValueNode(int.Parse(token.LiteralValue)), + from token in Satisfy(x => x.TokenType == LexicalTokenType.ConstFloat) + select new FloatValueNode(double.Parse(token.LiteralValue)) + ); + } + + protected static IParser CharParser() + { + return Satisfy(token => token.TokenType == LexicalTokenType.Character) + .Map(x => new CharValueNode(char.Parse(x.LiteralValue))); + } + + protected static IParser BasicTypeParser() + { + return Choice( + Keyword("integer"), + Keyword("real"), + Keyword("boolean"), + Keyword("char") + ); + } } diff --git a/CanonSharp.Pascal/SyntaxTree/CharValueNode.cs b/CanonSharp.Pascal/SyntaxTree/CharValueNode.cs new file mode 100644 index 0000000..d1bef14 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/CharValueNode.cs @@ -0,0 +1,8 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class CharValueNode(char value) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.CharValue; + + public char Value => value; +} diff --git a/CanonSharp.Pascal/SyntaxTree/ConstantNode.cs b/CanonSharp.Pascal/SyntaxTree/ConstantNode.cs new file mode 100644 index 0000000..c44c4da --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/ConstantNode.cs @@ -0,0 +1,12 @@ +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class ConstantNode(LexicalToken identifier, SyntaxNodeBase value) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.Constant; + + public LexicalToken Identifier = identifier; + + public SyntaxNodeBase Value = value; +} diff --git a/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs b/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs index 0ca95d6..df88d62 100644 --- a/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs +++ b/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs @@ -1,8 +1,12 @@ namespace CanonSharp.Pascal.SyntaxTree; -public sealed class ProgramBody(BlockNode block) : SyntaxNodeBase +public sealed class ProgramBody(BlockNode constantDeclarations, BlockNode variableDeclarations, BlockNode mainBlock) : SyntaxNodeBase { public override SyntaxNodeType NodeType => SyntaxNodeType.ProgramBody; - public BlockNode MainBlock => block; + public BlockNode ConstantDeclarations => constantDeclarations; + + public BlockNode VariableDeclarations => variableDeclarations; + + public BlockNode MainBlock => mainBlock; } diff --git a/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs b/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs index a8675e4..4c79926 100644 --- a/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs +++ b/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs @@ -7,9 +7,13 @@ public enum SyntaxNodeType IntegerValue, FloatValue, BooleanValue, + CharValue, Variable, + Type, Assign, Block, + Constant, + VariableDeclaration, ProgramBody, ProgramHead, Program diff --git a/CanonSharp.Pascal/SyntaxTree/TypeNode.cs b/CanonSharp.Pascal/SyntaxTree/TypeNode.cs new file mode 100644 index 0000000..7c5ff3e --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/TypeNode.cs @@ -0,0 +1,10 @@ +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class TypeNode(LexicalToken typeToken) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.Type; + + public LexicalToken TypeToken => typeToken; +} diff --git a/CanonSharp.Pascal/SyntaxTree/VariableDeclarationNode.cs b/CanonSharp.Pascal/SyntaxTree/VariableDeclarationNode.cs new file mode 100644 index 0000000..579a311 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/VariableDeclarationNode.cs @@ -0,0 +1,12 @@ +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class VariableDeclarationNode(IEnumerable identifiers, TypeNode typeNode) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.VariableDeclaration; + + public IList Identifiers => identifiers.ToList(); + + public TypeNode TypeNode => typeNode; +} diff --git a/CanonSharp.Tests/ParserTests/ConstDeclarationTests.cs b/CanonSharp.Tests/ParserTests/ConstDeclarationTests.cs new file mode 100644 index 0000000..5e29d1b --- /dev/null +++ b/CanonSharp.Tests/ParserTests/ConstDeclarationTests.cs @@ -0,0 +1,62 @@ +using CanonSharp.Pascal.Parser; +using CanonSharp.Pascal.SyntaxTree; +using CanonSharp.Tests.Utils; + +namespace CanonSharp.Tests.ParserTests; + +public class ConstDeclarationTests : GrammarParserTestBase +{ + [Fact] + public void BooleanConstValueTest() + { + BooleanValueNode node = RunParser(GrammarParser.ConstValueParser(), "true"); + Assert.True(node.Value); + + node = RunParser(GrammarParser.ConstValueParser(), "false"); + Assert.False(node.Value); + } + + [Fact] + public void CharConstValueTest() + { + CharValueNode node = RunParser(GrammarParser.ConstValueParser(), "'a'"); + Assert.Equal('a', node.Value); + } + + [Fact] + public void NumberConstValueTest() + { + IntegerValueNode integerValueNode = RunParser(GrammarParser.ConstValueParser(), "250"); + Assert.Equal(250, integerValueNode.Value); + + FloatValueNode floatValueNode = RunParser(GrammarParser.ConstValueParser(), "100.5"); + Assert.Equal(100.5, floatValueNode.Value); + + UnaryOperatorNode node = RunParser(GrammarParser.ConstValueParser(), "+250"); + Assert.Equal(UnaryOperatorType.Plus, node.OperatorType); + node = RunParser(GrammarParser.ConstValueParser(), "-100.5"); + Assert.Equal(UnaryOperatorType.Minus, node.OperatorType); + } + + [Fact] + public void ConstDeclarationTest() + { + ConstantNode node = RunParser(GrammarParser.ConstDeclarationParser(), "a = true"); + + Assert.Equal("a", node.Identifier.LiteralValue); + Assert.True(node.Value.Convert().Value); + } + + [Theory] + [InlineData("", 0)] + [InlineData("const INFINITE = 100;", 1)] + [InlineData("const a = true; b = false", 2)] + [InlineData("const code1 = 100.4;code2 = 'a'", 2)] + public void ConstDeclarationsCountTest(string input, int count) + { + BlockNode blockNode = RunParser(GrammarParser.ConstDeclarationsParser(), input); + List constantNodes = blockNode.Statements.Select(node => node.Convert()).ToList(); + + Assert.Equal(count, constantNodes.Count); + } +} diff --git a/CanonSharp.Tests/ParserTests/ProgramParserTests.cs b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs index fddf419..818cb35 100644 --- a/CanonSharp.Tests/ParserTests/ProgramParserTests.cs +++ b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs @@ -38,4 +38,32 @@ public class ProgramParserTests : GrammarParserTestBase Assert.Equal(1, binaryOperatorNode.Left.Convert().Value); Assert.Equal(1, binaryOperatorNode.Right.Convert().Value); } + + [Fact] + public void ProgramParseTest3() + { + Program program = ProgramParse(""" + program main; + const a = 1; b = true; + begin + temp := 1 + 1; + end. + """); + + Assert.Equal("main", program.Head.ProgramName.LiteralValue); + + List constantNodes = program.Body.ConstantDeclarations.Statements + .Select(x => x.Convert()).ToList(); + Assert.Equal(2, constantNodes.Count); + Assert.Equal("a", constantNodes[0].Identifier.LiteralValue); + Assert.Equal("b", constantNodes[1].Identifier.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); + } }