diff --git a/CanonSharp.Pascal/Parser/GrammarParser.cs b/CanonSharp.Pascal/Parser/GrammarParser.cs index 2ce60b2..78bd12e 100644 --- a/CanonSharp.Pascal/Parser/GrammarParser.cs +++ b/CanonSharp.Pascal/Parser/GrammarParser.cs @@ -32,6 +32,13 @@ public sealed class GrammarParser : GrammarParserBuilder from _2 in Delimiter(")") select node; + IParser procedureCallParser = + from identifier in IdentifierParser() + from _ in Delimiter("(") + from expressions in ExpressionParser().SeparatedBy(Delimiter(",")) + from _1 in Delimiter(")") + select new ProcedureCallNode(identifier, expressions); + return Choice( TrueParser(), FalseParser(), @@ -39,6 +46,7 @@ public sealed class GrammarParser : GrammarParserBuilder minusParser, plusParser, notParser, + procedureCallParser, VariableParser(), parenthesisParser ); @@ -189,6 +197,19 @@ public sealed class GrammarParser : GrammarParserBuilder select new WhileNode(condition, statement); } + public static IParser ProcedureCallParser() + { + return Choice( + from identifier in IdentifierParser() + from _ in Delimiter("(") + from expressions in ExpressionParser().SeparatedBy(Delimiter(",")) + from _1 in Delimiter(")") + select new ProcedureCallNode(identifier, expressions), + from identifier in IdentifierParser() + select new ProcedureCallNode(identifier, []) + ); + } + public static IParser StatementParser() { return Choice( @@ -196,6 +217,7 @@ public sealed class GrammarParser : GrammarParserBuilder from _ in Operator(":=") from expression in ExpressionParser() select new AssignNode(variable, expression), + ProcedureCallParser(), IfParser(), ForParser(), WhileParser(), @@ -236,7 +258,7 @@ public sealed class GrammarParser : GrammarParserBuilder select new ConstantNode(identifier, node); } - public static IParser ConstDeclarationsParser() + public static IParser ConstDeclarationsParser() { return (from _ in Keyword("const") from tokens in ConstDeclarationParser().SeparatedOrEndBy1(Delimiter(";")) @@ -283,12 +305,73 @@ public sealed class GrammarParser : GrammarParserBuilder select new BlockNode(nodes)).Try(new BlockNode([])); } - public static IParser ProgramBodyParser() + public static IParser> ParameterParser() + { + return Choice( + from _ in Keyword("var") + from tokens in IdentifierParser().SeparatedBy1(Delimiter(",")) + from _1 in Delimiter(":") + from typeToken in TypeParser() + select tokens.Select(x => new Parameter(true, x, typeToken)), + from tokens in IdentifierParser().SeparatedBy1(Delimiter(",")) + from _ in Delimiter(":") + from typeToken in TypeParser() + select tokens.Select(x => new Parameter(false, x, typeToken)) + ); + } + + public static IParser> FormalParameterParser() + { + return (from _ in Delimiter("(") + from parameters in ParameterParser().SeparatedBy(Delimiter(";")) + from _1 in Delimiter(")") + select parameters.Aggregate(new List(), (result, array) => + { + result.AddRange(array); + return result; + })).Try([]); + } + + public static IParser SubprogramHeadParser() + { + return Choice( + from _ in Keyword("procedure") + from identifier in IdentifierParser() + from parameters in FormalParameterParser() + select new SubprogramHead(identifier, parameters), + from _ in Keyword("function") + from identifier in IdentifierParser() + from parameters in FormalParameterParser() + from _1 in Delimiter(":") + from typeToken in TypeParser() + select new SubprogramHead(identifier, parameters, typeToken) + ); + } + + public static IParser SubprogramBodyParser() { return from constant in ConstDeclarationsParser() from variables in VariableDeclarationsParser() from block in CompoundStatementParser() - select new ProgramBody(constant.Convert(), variables, block); + select new SubprogramBody(constant, variables, block); + } + + public static IParser SubprogramParser() + { + return from head in SubprogramHeadParser() + from _ in Delimiter(";") + from body in SubprogramBodyParser() + select new Subprogram(head, body); + } + + public static IParser ProgramBodyParser() + { + return from constant in ConstDeclarationsParser() + from variables in VariableDeclarationsParser() + from subprograms in SubprogramParser().SeparatedOrEndBy(Delimiter(";")) + .Map(x => new BlockNode(x)) + from block in CompoundStatementParser() + select new ProgramBody(constant, variables, subprograms, block); } public static IParser ProgramHeadParser() diff --git a/CanonSharp.Pascal/SyntaxTree/Parameter.cs b/CanonSharp.Pascal/SyntaxTree/Parameter.cs new file mode 100644 index 0000000..3674f29 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/Parameter.cs @@ -0,0 +1,14 @@ +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class Parameter(bool isReference, LexicalToken identifier, SyntaxNodeBase typeNode) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.Parameter; + + public bool IsReference => isReference; + + public LexicalToken Identifier => identifier; + + public SyntaxNodeBase TypeNode => typeNode; +} diff --git a/CanonSharp.Pascal/SyntaxTree/ProcedureCallNode.cs b/CanonSharp.Pascal/SyntaxTree/ProcedureCallNode.cs new file mode 100644 index 0000000..9824e3e --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/ProcedureCallNode.cs @@ -0,0 +1,18 @@ +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class ProcedureCallNode : SyntaxNodeBase +{ + public ProcedureCallNode(LexicalToken identifier, IEnumerable parameters) + { + Identifier = identifier; + Parameters.AddRange(parameters); + } + + public override SyntaxNodeType NodeType => SyntaxNodeType.ProcedureCall; + + public LexicalToken Identifier { get; } + + public List Parameters { get; } = []; +} diff --git a/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs b/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs index df88d62..cb42698 100644 --- a/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs +++ b/CanonSharp.Pascal/SyntaxTree/ProgramBody.cs @@ -1,6 +1,9 @@ namespace CanonSharp.Pascal.SyntaxTree; -public sealed class ProgramBody(BlockNode constantDeclarations, BlockNode variableDeclarations, BlockNode mainBlock) : SyntaxNodeBase +public sealed class ProgramBody(BlockNode constantDeclarations, BlockNode variableDeclarations, + BlockNode subprograms, + BlockNode mainBlock) + : SyntaxNodeBase { public override SyntaxNodeType NodeType => SyntaxNodeType.ProgramBody; @@ -8,5 +11,7 @@ public sealed class ProgramBody(BlockNode constantDeclarations, BlockNode variab public BlockNode VariableDeclarations => variableDeclarations; + public BlockNode Subprograms => subprograms; + public BlockNode MainBlock => mainBlock; } diff --git a/CanonSharp.Pascal/SyntaxTree/Subprogram.cs b/CanonSharp.Pascal/SyntaxTree/Subprogram.cs new file mode 100644 index 0000000..97b5cdc --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/Subprogram.cs @@ -0,0 +1,10 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class Subprogram(SubprogramHead subprogramHead, SubprogramBody subprogramBody) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.SubProgram; + + public SubprogramHead Head => subprogramHead; + + public SubprogramBody Body => subprogramBody; +} diff --git a/CanonSharp.Pascal/SyntaxTree/SubprogramBody.cs b/CanonSharp.Pascal/SyntaxTree/SubprogramBody.cs new file mode 100644 index 0000000..f580864 --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/SubprogramBody.cs @@ -0,0 +1,12 @@ +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class SubprogramBody(BlockNode constDeclarations, BlockNode variableDeclarations, BlockNode mainBlock) : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.SubprogramBody; + + public BlockNode ConstDeclarations => constDeclarations; + + public BlockNode VariableDeclarations => variableDeclarations; + + public BlockNode MainBlock => mainBlock; +} diff --git a/CanonSharp.Pascal/SyntaxTree/SubprogramHead.cs b/CanonSharp.Pascal/SyntaxTree/SubprogramHead.cs new file mode 100644 index 0000000..27d50bb --- /dev/null +++ b/CanonSharp.Pascal/SyntaxTree/SubprogramHead.cs @@ -0,0 +1,28 @@ +using CanonSharp.Pascal.Scanner; + +namespace CanonSharp.Pascal.SyntaxTree; + +public sealed class SubprogramHead : SyntaxNodeBase +{ + public override SyntaxNodeType NodeType => SyntaxNodeType.SubprogramHead; + + public LexicalToken Identifier { get; } + + public List Parameters { get; } = []; + + public SyntaxNodeBase? TypeToken { get; } + + public SubprogramHead(LexicalToken identifier, IEnumerable parameters) + { + Identifier = identifier; + Parameters.AddRange(parameters); + TypeToken = null; + } + + public SubprogramHead(LexicalToken identifier, IEnumerable parameters, SyntaxNodeBase typeToken) : this( + identifier, parameters) + { + TypeToken = typeToken; + } + +} diff --git a/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs b/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs index 31e412d..d57266e 100644 --- a/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs +++ b/CanonSharp.Pascal/SyntaxTree/SyntaxNodeBase.cs @@ -17,6 +17,11 @@ public enum SyntaxNodeType If, While, For, + ProcedureCall, + Parameter, + SubprogramHead, + SubprogramBody, + SubProgram, ProgramBody, ProgramHead, Program diff --git a/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs b/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs index 5961a1d..16af0a3 100644 --- a/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs +++ b/CanonSharp.Tests/ParserTests/ExpressionParserTests.cs @@ -82,6 +82,23 @@ public sealed class ExpressionParserTests : GrammarParserTestBase Assert.Equal(1, binaryOperatorNode.Right.Convert().Value); } + [Fact] + public void ProcedureCallTest() + { + ProcedureCallNode node = RunParser(GrammarParser.FactorParser(), "test(1)"); + Assert.Equal("test", node.Identifier.LiteralValue); + Assert.Single(node.Parameters); + Assert.Equal(1, node.Parameters[0].Convert().Value); + + node = RunParser(GrammarParser.FactorParser(), "test()"); + Assert.Equal("test", node.Identifier.LiteralValue); + Assert.Empty(node.Parameters); + + VariableNode variableNode = RunParser(GrammarParser.FactorParser(), "test"); + Assert.Equal("test", variableNode.Identifier.LiteralValue); + Assert.Empty(variableNode.Indexers); + } + [Fact] public void SingleTermTest() { diff --git a/CanonSharp.Tests/ParserTests/ProgramParserTests.cs b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs index 03d21c5..1dad715 100644 --- a/CanonSharp.Tests/ParserTests/ProgramParserTests.cs +++ b/CanonSharp.Tests/ParserTests/ProgramParserTests.cs @@ -153,6 +153,75 @@ public class ProgramParserTests : GrammarParserTestBase i := i + 1; end. """)] + [InlineData(""" + program main; + procedure test; + begin + end; + begin + end. + """)] + [InlineData(""" + program main; + function test(a, b : integer) : integer; + begin + test := 1; + end; + begin + end. + """)] + [InlineData(""" + program main; + var i : integer; + begin + for i := 1 to 100 do + begin + doSomething(i); + end; + end. + """)] + [InlineData(""" + program main; + begin + if 1 = 2 then + begin + test1; + test2 := a; + end + else + begin + doSomething; + end; + end. + """)] + [InlineData(""" + program main; + var a : integer; + function test : integer; + begin + end; + begin + test; + a := test; + test(); + a := test(); + end. + """)] + [InlineData(""" + program main; + procedure test(); + begin + end; + begin + test(); + end. + """)] + [InlineData(""" + program main; + begin + test + end. + """)] public void ProgramParseTest(string input) { ProgramParse(input); diff --git a/CanonSharp.Tests/ParserTests/StatementParserTests.cs b/CanonSharp.Tests/ParserTests/StatementParserTests.cs index b54ef27..b4bc86b 100644 --- a/CanonSharp.Tests/ParserTests/StatementParserTests.cs +++ b/CanonSharp.Tests/ParserTests/StatementParserTests.cs @@ -83,6 +83,46 @@ public class StatementParserTests : GrammarParserTestBase Assert.Equal("a", assignNode.Variable.Identifier.LiteralValue); } + [Fact] + public void ProcedureCallTest1() + { + ProcedureCallNode node = RunParser(GrammarParser.ProcedureCallParser(), "test()"); + Assert.Equal("test", node.Identifier.LiteralValue); + Assert.Empty(node.Parameters); + + node = RunParser(GrammarParser.ProcedureCallParser(), "test"); + Assert.Equal("test", node.Identifier.LiteralValue); + Assert.Empty(node.Parameters); + } + + [Fact] + public void ProcedureCallTest2() + { + ProcedureCallNode node = RunParser(GrammarParser.ProcedureCallParser(), + "test(1 + 1, true)"); + + Assert.Equal(2, node.Parameters.Count); + + BinaryOperatorNode firstParameter = node.Parameters[0].Convert(); + Assert.Equal(BinaryOperatorType.Add, firstParameter.OperatorType); + + BooleanValueNode secondParameter = node.Parameters[1].Convert(); + Assert.True(secondParameter.Value); + } + + [Fact] + public void ProcedureCallTest3() + { + ProcedureCallNode node = + RunParser(GrammarParser.ProcedureCallParser(), "test(1 * + 1, test2[0])"); + + Assert.Equal(2, node.Parameters.Count); + + VariableNode secondParameter = node.Parameters[1].Convert(); + Assert.Equal("test2", secondParameter.Identifier.LiteralValue); + Assert.Single(secondParameter.Indexers); + } + [Theory] [InlineData(""" begin diff --git a/CanonSharp.Tests/ParserTests/SubprogramParserTests.cs b/CanonSharp.Tests/ParserTests/SubprogramParserTests.cs new file mode 100644 index 0000000..c5fa723 --- /dev/null +++ b/CanonSharp.Tests/ParserTests/SubprogramParserTests.cs @@ -0,0 +1,83 @@ +using CanonSharp.Pascal.Parser; +using CanonSharp.Pascal.SyntaxTree; +using CanonSharp.Tests.Utils; + +namespace CanonSharp.Tests.ParserTests; + +public class SubprogramParserTests : GrammarParserTestBase +{ + [Fact] + public void ParameterTest1() + { + List parameters = RunParser(GrammarParser.ParameterParser(), "a ,b : integer"); + + Assert.Equal(2, parameters.Count); + Assert.All(parameters, p => + { + Assert.False(p.IsReference); + Assert.Equal("integer", p.TypeNode.Convert().TypeToken.LiteralValue); + }); + + parameters = RunParser(GrammarParser.ParameterParser(), "var a, b,c: real"); + Assert.Equal(3, parameters.Count); + Assert.All(parameters, p => + { + Assert.True(p.IsReference); + Assert.Equal("real", p.TypeNode.Convert().TypeToken.LiteralValue); + }); + } + + [Theory] + [InlineData("(var a, b : integer; c, d : real)", 4)] + [InlineData("(a : boolean; var c, d, e : char)", 4)] + [InlineData("(a : integer)", 1)] + [InlineData("(var b ,c : real)", 2)] + public void FormalParameterTest(string input, int count) + { + List parameters = RunParser(GrammarParser.FormalParameterParser(), input); + Assert.Equal(count, parameters.Count); + } + + [Theory] + [InlineData("procedure test", 0)] + [InlineData("procedure test()", 0)] + [InlineData("procedure test(a : integer; var b : real)", 2)] + [InlineData("procedure test(a,b, c : real; var e, f : boolean)", 5)] + [InlineData("function test : integer", 0)] + [InlineData("function test() : real", 0)] + [InlineData("function test(a : integer; var b : real) : boolean", 2)] + [InlineData("function test(a,b, c : real; var e, f : boolean) : char", 5)] + public void SubprogramHeadTest(string input, int count) + { + SubprogramHead head = RunParser(GrammarParser.SubprogramHeadParser(), input); + Assert.Equal(count, head.Parameters.Count); + } + + [Fact] + public void SubprogramHeadTest1() + { + SubprogramHead head = + RunParser(GrammarParser.SubprogramHeadParser(), "function test(a : integer) : real"); + + Assert.Equal("test", head.Identifier.LiteralValue); + Assert.Single(head.Parameters); + Assert.NotNull(head.TypeToken); + Assert.Equal("real", head.TypeToken.Convert().TypeToken.LiteralValue); + } + + [Fact] + public void SubprogramBodyTest1() + { + SubprogramBody body = RunParser(GrammarParser.SubprogramBodyParser(), """ + const a = 3; + var c, d : integer; e, f :real; + begin + c := a + d; + end + """); + + Assert.Single(body.ConstDeclarations.Statements); + Assert.Equal(2, body.VariableDeclarations.Statements.Count); + Assert.Equal(1, body.MainBlock.Statements.Count); + } +}