diff --git a/CanonSharp.Pascal/Parser/GrammarParser.cs b/CanonSharp.Pascal/Parser/GrammarParser.cs index f99da8e..4354f32 100644 --- a/CanonSharp.Pascal/Parser/GrammarParser.cs +++ b/CanonSharp.Pascal/Parser/GrammarParser.cs @@ -139,8 +139,15 @@ public sealed class GrammarParser : GrammarParserBuilder public static IParser VariableParser() { - return from token in Satisfy(token => token.TokenType == LexicalTokenType.Identifier) - select new VariableNode(token.LiteralValue); + return Choice( + from token in IdentifierParser() + from _ in Delimiter("[") + from expressions in ExpressionsParser() + from _1 in Delimiter("]") + select new VariableNode(token, expressions), + from token in IdentifierParser() + select new VariableNode(token) + ); } public static IParser StatementParser() @@ -193,10 +200,28 @@ public sealed class GrammarParser : GrammarParserBuilder select new BlockNode(tokens)).Try(new BlockNode([])); } + public static IParser ArrayTypeParser() + { + IParser> arrayRangeParser = ( + from left in IntegerParser() + from _ in Delimiter("..") + from right in IntegerParser() + select new ArrayRange(left.Value, right.Value)).SeparatedBy1(Delimiter(",")); + + return from _ in Keyword("array") + from _1 in Delimiter("[") + from ranges in arrayRangeParser + from _2 in Delimiter("]") + from _3 in Keyword("of") + from typeToken in BasicTypeParser() + select new TypeNode(typeToken, ranges); + } + public static IParser TypeParser() { - return from token in BasicTypeParser() - select new TypeNode(token); + return Choice(ArrayTypeParser(), + from token in BasicTypeParser() + select new TypeNode(token)); } public static IParser VariableDeclarationParser() diff --git a/CanonSharp.Pascal/Parser/GrammarParserBase.cs b/CanonSharp.Pascal/Parser/GrammarParserBase.cs index 83e1338..c3be8a6 100644 --- a/CanonSharp.Pascal/Parser/GrammarParserBase.cs +++ b/CanonSharp.Pascal/Parser/GrammarParserBase.cs @@ -31,6 +31,12 @@ public abstract class GrammarParserBuilder select new BooleanValueNode(false); } + protected static IParser IntegerParser() + { + return from token in Satisfy(token => token.TokenType == LexicalTokenType.ConstInteger) + select new IntegerValueNode(int.Parse(token.LiteralValue)); + } + protected static IParser NumberParser() { return Choice( @@ -56,4 +62,9 @@ public abstract class GrammarParserBuilder Keyword("char") ); } + + protected static IParser IdentifierParser() + { + return Satisfy(token => token.TokenType == LexicalTokenType.Identifier); + } } diff --git a/CanonSharp.Pascal/Scanner/LexicalScanner.cs b/CanonSharp.Pascal/Scanner/LexicalScanner.cs index b302674..6bf869f 100644 --- a/CanonSharp.Pascal/Scanner/LexicalScanner.cs +++ b/CanonSharp.Pascal/Scanner/LexicalScanner.cs @@ -98,9 +98,14 @@ public sealed class LexicalScanner public static IParser ConstIntegerParser() { - return from nums in AsciiDigit().Many1() + return Choice( + from nums in AsciiDigit().Many1() + from _ in String("..").LookAhead() + select new LexicalToken(LexicalTokenType.ConstInteger, new string(nums.ToArray())), + from nums in AsciiDigit().Many1() from _ in Char('.').LookAhead().Not() - select new LexicalToken(LexicalTokenType.ConstInteger, new string(nums.ToArray())); + select new LexicalToken(LexicalTokenType.ConstInteger, new string(nums.ToArray())) + ); } public static IParser ConstFloatParser() @@ -142,8 +147,8 @@ public sealed class LexicalScanner return JunkParser().SkipTill(Choice(KeywordParser(), DelimiterParser(), OperatorParser(), - ConstIntegerParser(), ConstFloatParser(), + ConstIntegerParser(), CharParser(), IdentifierParser())).Many(); } diff --git a/CanonSharp.Pascal/SyntaxTree/TypeNode.cs b/CanonSharp.Pascal/SyntaxTree/TypeNode.cs index 7c5ff3e..b0e2bed 100644 --- a/CanonSharp.Pascal/SyntaxTree/TypeNode.cs +++ b/CanonSharp.Pascal/SyntaxTree/TypeNode.cs @@ -2,9 +2,23 @@ namespace CanonSharp.Pascal.SyntaxTree; -public sealed class TypeNode(LexicalToken typeToken) : SyntaxNodeBase +public record struct ArrayRange(int Left, int Right); + +public sealed class TypeNode : SyntaxNodeBase { public override SyntaxNodeType NodeType => SyntaxNodeType.Type; - public LexicalToken TypeToken => typeToken; + public LexicalToken TypeToken { get; } + + public List ArrayRanges { get; } = []; + + public TypeNode(LexicalToken typeToken) + { + TypeToken = typeToken; + } + + public TypeNode(LexicalToken typeToken, IEnumerable arrayRanges) : this(typeToken) + { + ArrayRanges.AddRange(arrayRanges); + } } diff --git a/CanonSharp.Pascal/SyntaxTree/VariableNode.cs b/CanonSharp.Pascal/SyntaxTree/VariableNode.cs index e14d2cf..513e8ec 100644 --- a/CanonSharp.Pascal/SyntaxTree/VariableNode.cs +++ b/CanonSharp.Pascal/SyntaxTree/VariableNode.cs @@ -1,8 +1,23 @@ -namespace CanonSharp.Pascal.SyntaxTree; +using CanonSharp.Pascal.Scanner; -public sealed class VariableNode(string name) : SyntaxNodeBase +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class VariableNode : SyntaxNodeBase { public override SyntaxNodeType NodeType => SyntaxNodeType.Variable; - public string IdentifierName => name; + public LexicalToken Identifier { get; } + + public List Indexers { get; } = []; + + public VariableNode(LexicalToken identifier) + { + Identifier = identifier; + } + + public VariableNode(LexicalToken identifier, IEnumerable expressions) + { + Identifier = identifier; + Indexers.AddRange(expressions); + } } diff --git a/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs b/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs index aaa82fb..5961a1d 100644 --- a/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs +++ b/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs @@ -41,15 +41,52 @@ public sealed class ExpressionParserTests : GrammarParserTestBase [Fact] public void IdentifierTest() { - VariableNode node = RunParser(GrammarParser.FactorParser(), "temp"); - Assert.Equal("temp", node.IdentifierName); + VariableNode node = RunParser(GrammarParser.VariableParser(), "temp"); + Assert.Equal("temp", node.Identifier.LiteralValue); + } + + [Fact] + public void ArrayIdentifierTest() + { + VariableNode node = RunParser(GrammarParser.VariableParser(), "a[0]"); + Assert.Equal("a", node.Identifier.LiteralValue); + Assert.Single(node.Indexers); + Assert.Equal(0, node.Indexers.First().Convert().Value); + + node = RunParser(GrammarParser.VariableParser(), "a[i + 1]"); + Assert.Equal("a", node.Identifier.LiteralValue); + Assert.Single(node.Indexers); + BinaryOperatorNode binaryOperatorNode = node.Indexers.First().Convert(); + Assert.Equal(BinaryOperatorType.Add, binaryOperatorNode.OperatorType); + Assert.Equal("i", binaryOperatorNode.Left.Convert().Identifier.LiteralValue); + Assert.Equal(1, binaryOperatorNode.Right.Convert().Value); + + node = RunParser(GrammarParser.VariableParser(), "a[b[i + 1] - 10, c]"); + Assert.Equal("a", node.Identifier.LiteralValue); + Assert.Equal(2, node.Indexers.Count); + + VariableNode secondNode = node.Indexers[1].Convert(); + Assert.Equal("c", secondNode.Identifier.LiteralValue); + Assert.Empty(secondNode.Indexers); + + BinaryOperatorNode firstNode = node.Indexers[0].Convert(); + Assert.Equal(BinaryOperatorType.Subtract, firstNode.OperatorType); + Assert.Equal(10, firstNode.Right.Convert().Value); + + VariableNode variableNode = firstNode.Left.Convert(); + Assert.Equal("b", variableNode.Identifier.LiteralValue); + Assert.Single(variableNode.Indexers); + binaryOperatorNode = variableNode.Indexers[0].Convert(); + Assert.Equal(BinaryOperatorType.Add, binaryOperatorNode.OperatorType); + Assert.Equal("i", binaryOperatorNode.Left.Convert().Identifier.LiteralValue); + Assert.Equal(1, binaryOperatorNode.Right.Convert().Value); } [Fact] public void SingleTermTest() { VariableNode node = RunParser(GrammarParser.TermParser(), "temp"); - Assert.Equal("temp", node.IdentifierName); + Assert.Equal("temp", node.Identifier.LiteralValue); UnaryOperatorNode unaryOperatorNode = RunParser(GrammarParser.TermParser(), "- 123"); Assert.Equal(UnaryOperatorType.Minus, unaryOperatorNode.OperatorType); @@ -89,7 +126,7 @@ public sealed class ExpressionParserTests : GrammarParserTestBase 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("temp", leftNode.Left.Convert().Identifier.LiteralValue); Assert.Equal(2, leftNode.Right.Convert().Value); } @@ -97,7 +134,7 @@ public sealed class ExpressionParserTests : GrammarParserTestBase public void SimpleExpressionTest1() { VariableNode node = RunParser(GrammarParser.SimpleExpressionParser(), "temp"); - Assert.Equal("temp", node.IdentifierName); + Assert.Equal("temp", node.Identifier.LiteralValue); UnaryOperatorNode unaryOperatorNode = RunParser(GrammarParser.SimpleExpressionParser(), "- 123"); @@ -238,8 +275,6 @@ public sealed class ExpressionParserTests : GrammarParserTestBase public void VariableTest1() { VariableNode node = RunParser(GrammarParser.VariableParser(), "temp"); - Assert.Equal("temp", node.IdentifierName); + Assert.Equal("temp", node.Identifier.LiteralValue); } - - } diff --git a/CanonSharp.Tests/ParserTests/ProgramParserTests.cs b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs index 6431711..8a9fa92 100644 --- a/CanonSharp.Tests/ParserTests/ProgramParserTests.cs +++ b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs @@ -31,7 +31,7 @@ public class ProgramParserTests : GrammarParserTestBase Assert.Equal("main", program.Head.ProgramName.LiteralValue); AssignNode assignNode = program.Body.MainBlock.Statements[0].Convert(); - Assert.Equal("temp", assignNode.Variable.IdentifierName); + Assert.Equal("temp", assignNode.Variable.Identifier.LiteralValue); BinaryOperatorNode binaryOperatorNode = assignNode.Expression.Convert(); Assert.Equal(BinaryOperatorType.Add, binaryOperatorNode.OperatorType); @@ -59,7 +59,7 @@ public class ProgramParserTests : GrammarParserTestBase Assert.Equal("b", constantNodes[1].Identifier.LiteralValue); AssignNode assignNode = program.Body.MainBlock.Statements[0].Convert(); - Assert.Equal("temp", assignNode.Variable.IdentifierName); + Assert.Equal("temp", assignNode.Variable.Identifier.LiteralValue); BinaryOperatorNode binaryOperatorNode = assignNode.Expression.Convert(); Assert.Equal(BinaryOperatorType.Add, binaryOperatorNode.OperatorType); @@ -124,6 +124,20 @@ public class ProgramParserTests : GrammarParserTestBase d := false; end. """)] + [InlineData(""" + program arrayTest; + var a : array [0..10] of integer; + begin + a[0] := 1; + end. + """)] + [InlineData(""" + program arrayTest; + var a : array [10..100, 0..10] of integer; + begin + a[10,0] := 1; + end. + """)] public void ProgramParseTest(string input) { ProgramParse(input); diff --git a/CanonSharp.Tests/ParserTests/StatementParserTests.cs b/CanonSharp.Tests/ParserTests/StatementParserTests.cs index ce2d7e5..80f6128 100644 --- a/CanonSharp.Tests/ParserTests/StatementParserTests.cs +++ b/CanonSharp.Tests/ParserTests/StatementParserTests.cs @@ -11,7 +11,7 @@ public class StatementParserTests : GrammarParserTestBase { AssignNode node = RunParser(GrammarParser.StatementParser(), "temp := 1"); - Assert.Equal("temp", node.Variable.IdentifierName); + Assert.Equal("temp", node.Variable.Identifier.LiteralValue); Assert.Equal(1, node.Expression.Convert().Value); } @@ -34,10 +34,10 @@ public class StatementParserTests : GrammarParserTestBase Assert.Equal(2, node.Statements.Count); AssignNode assignNode = node.Statements[0].Convert(); - Assert.Equal("temp", assignNode.Variable.IdentifierName); + Assert.Equal("temp", assignNode.Variable.Identifier.LiteralValue); assignNode = node.Statements[1].Convert(); - Assert.Equal("flag", assignNode.Variable.IdentifierName); + Assert.Equal("flag", assignNode.Variable.Identifier.LiteralValue); } [Fact] @@ -74,9 +74,9 @@ public class StatementParserTests : GrammarParserTestBase Assert.Equal(2, node.Statements.Count); AssignNode assignNode = node.Statements[0].Convert(); - Assert.Equal("temp", assignNode.Variable.IdentifierName); + Assert.Equal("temp", assignNode.Variable.Identifier.LiteralValue); assignNode = node.Statements[1].Convert(); - Assert.Equal("flag", assignNode.Variable.IdentifierName); + Assert.Equal("flag", assignNode.Variable.Identifier.LiteralValue); } } diff --git a/CanonSharp.Tests/ParserTests/VariableDeclarationTests.cs b/CanonSharp.Tests/ParserTests/VariableDeclarationTests.cs index 6c5fe5d..49693a5 100644 --- a/CanonSharp.Tests/ParserTests/VariableDeclarationTests.cs +++ b/CanonSharp.Tests/ParserTests/VariableDeclarationTests.cs @@ -11,12 +11,28 @@ public class VariableDeclarationTests : GrammarParserTestBase [InlineData("integer", "integer")] [InlineData("char", "char")] [InlineData("real", "real")] - public void TypeParseTest(string input, string value) + public void BasicTypeParseTest(string input, string value) { TypeNode node = RunParser(GrammarParser.TypeParser(), input); Assert.Equal(value, node.TypeToken.LiteralValue); } + [Fact] + public void ArrayTypeParseTest() + { + TypeNode node = RunParser(GrammarParser.ArrayTypeParser(), "array [0..9] of integer"); + + Assert.Equal("integer", node.TypeToken.LiteralValue); + Assert.Single(node.ArrayRanges); + Assert.Equal(new ArrayRange(0, 9), node.ArrayRanges.First()); + + node = RunParser(GrammarParser.ArrayTypeParser(), "array [0..10,0..10] of char"); + Assert.Equal("char", node.TypeToken.LiteralValue); + Assert.Equal(2, node.ArrayRanges.Count); + Assert.Equal(new ArrayRange(0, 10), node.ArrayRanges[0]); + Assert.Equal(new ArrayRange(0, 10), node.ArrayRanges[1]); + } + [Fact] public void VariableDeclarationTest() { @@ -35,12 +51,29 @@ public class VariableDeclarationTests : GrammarParserTestBase Assert.Equal("boolean", node.TypeNode.TypeToken.LiteralValue); } + [Fact] + public void ArrayVariableDeclarationTest() + { + VariableDeclarationNode node = RunParser(GrammarParser.VariableDeclarationParser(), + "test_array: array [0..9,10..20] of real"); + + Assert.Single(node.Identifiers); + Assert.Contains(node.Identifiers, token => token.LiteralValue == "test_array"); + Assert.Equal("real", node.TypeNode.TypeToken.LiteralValue); + + Assert.Equal(2, node.TypeNode.ArrayRanges.Count); + Assert.Equal(new ArrayRange(0, 9), node.TypeNode.ArrayRanges[0]); + Assert.Equal(new ArrayRange(10, 20), node.TypeNode.ArrayRanges[1]); + } + [Theory] [InlineData("var a : integer;", 1)] [InlineData("var a : integer; b : boolean;", 2)] [InlineData("var a : integer; b,c : boolean;" + "d : char", 3)] [InlineData("var a,d,e : integer; b : boolean; c: real;", 3)] + [InlineData("var a : array [0..5] of integer", 1)] + [InlineData("var a,b,c: array [0..9,10..20] of integer; c,d : boolean", 2)] public void VariableDeclarationsTest(string input, int count) { BlockNode node = RunParser(GrammarParser.VariableDeclarationsParser(), input); diff --git a/CanonSharp.Tests/ScannerTests/LexicalParserTests.cs b/CanonSharp.Tests/ScannerTests/LexicalParserTests.cs index 99a6a44..0935e40 100644 --- a/CanonSharp.Tests/ScannerTests/LexicalParserTests.cs +++ b/CanonSharp.Tests/ScannerTests/LexicalParserTests.cs @@ -131,6 +131,21 @@ public class LexicalParserTests : LexicalTestBase ]); } + [Fact] + public void LexicalParserTest4() + { + ValidateLexicalTokens(LexicalScanner.PascalParser(), "array [0..9] of integer", [ + (LexicalTokenType.Keyword, "array"), + (LexicalTokenType.Delimiter, "["), + (LexicalTokenType.ConstInteger, "0"), + (LexicalTokenType.Delimiter, ".."), + (LexicalTokenType.ConstInteger, "9"), + (LexicalTokenType.Delimiter, "]"), + (LexicalTokenType.Keyword, "of"), + (LexicalTokenType.Keyword, "integer") + ]); + } + [Theory] [InlineData(""" program exFunction; diff --git a/CanonSharp.Tests/ScannerTests/LexicalTokenParserTest.cs b/CanonSharp.Tests/ScannerTests/LexicalTokenParserTest.cs index bc3574a..26d94dc 100644 --- a/CanonSharp.Tests/ScannerTests/LexicalTokenParserTest.cs +++ b/CanonSharp.Tests/ScannerTests/LexicalTokenParserTest.cs @@ -107,6 +107,7 @@ public class LexicalTokenParserTest : LexicalTestBase [Theory] [InlineData(123, "123")] [InlineData(0, "0")] + [InlineData(10, "10..20")] public void ConstIntegerTest(int value, string input) { StringReadState state = new(input); diff --git a/CanonSharp.Tests/Utils/LexicalTestBase.cs b/CanonSharp.Tests/Utils/LexicalTestBase.cs index ac521a8..b50f8c1 100644 --- a/CanonSharp.Tests/Utils/LexicalTestBase.cs +++ b/CanonSharp.Tests/Utils/LexicalTestBase.cs @@ -24,13 +24,15 @@ public abstract class LexicalTestBase } protected static void ValidateLexicalTokens(IParser> parser, string input, - IEnumerable<(LexicalTokenType, string)> exceptedResult) + List<(LexicalTokenType, string)> exceptedResult) { StringReadState state = new(input); IParseResult> result = parser.Parse(state); + List actualResult = result.Value.ToList(); + Assert.Equal(exceptedResult.Count, actualResult.Count); foreach (((LexicalTokenType exceptedType, string exceptedValue), LexicalToken token) in exceptedResult.Zip( - result.Value)) + actualResult)) { Assert.Equal(exceptedType, token.TokenType); Assert.Equal(exceptedValue, token.LiteralValue);