Compare commits

...

2 Commits

15 changed files with 575 additions and 6 deletions

View File

@ -32,6 +32,13 @@ public sealed class GrammarParser : GrammarParserBuilder
from _2 in Delimiter(")") from _2 in Delimiter(")")
select node; select node;
IParser<LexicalToken, SyntaxNodeBase> 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( return Choice(
TrueParser(), TrueParser(),
FalseParser(), FalseParser(),
@ -39,6 +46,7 @@ public sealed class GrammarParser : GrammarParserBuilder
minusParser, minusParser,
plusParser, plusParser,
notParser, notParser,
procedureCallParser,
VariableParser(), VariableParser(),
parenthesisParser parenthesisParser
); );
@ -150,13 +158,70 @@ public sealed class GrammarParser : GrammarParserBuilder
); );
} }
public static IParser<LexicalToken, SyntaxNodeBase> StatementParser() public static IParser<LexicalToken, IfNode> IfParser()
{
IParser<LexicalToken, IfNode> commonPart = from _ in Keyword("if")
from condition in ExpressionParser()
from _1 in Keyword("then")
from statement in StatementParser()
select new IfNode(condition, statement);
return Choice(
from common in commonPart
from _ in Keyword("else")
from elseStatement in StatementParser()
select new IfNode(common.Condition, common.Statement, elseStatement),
commonPart
);
}
public static IParser<LexicalToken, ForNode> ForParser()
{
return from _ in Keyword("for")
from identifier in IdentifierParser()
from _1 in Operator(":=")
from left in ExpressionParser()
from _2 in Keyword("to")
from right in ExpressionParser()
from _3 in Keyword("do")
from statement in StatementParser()
select new ForNode(identifier, left, right, statement);
}
public static IParser<LexicalToken, WhileNode> WhileParser()
{
return from _ in Keyword("while")
from condition in ExpressionParser()
from _1 in Keyword("do")
from statement in StatementParser()
select new WhileNode(condition, statement);
}
public static IParser<LexicalToken, ProcedureCallNode> ProcedureCallParser()
{ {
return Choice( 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<LexicalToken, SyntaxNodeBase> StatementParser()
{
return Choice<LexicalToken, SyntaxNodeBase>(
from variable in VariableParser() from variable in VariableParser()
from _ in Operator(":=") from _ in Operator(":=")
from expression in ExpressionParser() from expression in ExpressionParser()
select new AssignNode(variable, expression) select new AssignNode(variable, expression),
ProcedureCallParser(),
IfParser(),
ForParser(),
WhileParser(),
CompoundStatementParser()
); );
} }
@ -193,7 +258,7 @@ public sealed class GrammarParser : GrammarParserBuilder
select new ConstantNode(identifier, node); select new ConstantNode(identifier, node);
} }
public static IParser<LexicalToken, SyntaxNodeBase> ConstDeclarationsParser() public static IParser<LexicalToken, BlockNode> ConstDeclarationsParser()
{ {
return (from _ in Keyword("const") return (from _ in Keyword("const")
from tokens in ConstDeclarationParser().SeparatedOrEndBy1(Delimiter(";")) from tokens in ConstDeclarationParser().SeparatedOrEndBy1(Delimiter(";"))
@ -240,12 +305,73 @@ public sealed class GrammarParser : GrammarParserBuilder
select new BlockNode(nodes)).Try(new BlockNode([])); select new BlockNode(nodes)).Try(new BlockNode([]));
} }
public static IParser<LexicalToken, ProgramBody> ProgramBodyParser() public static IParser<LexicalToken, IEnumerable<Parameter>> 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<LexicalToken, IEnumerable<Parameter>> FormalParameterParser()
{
return (from _ in Delimiter("(")
from parameters in ParameterParser().SeparatedBy(Delimiter(";"))
from _1 in Delimiter(")")
select parameters.Aggregate(new List<Parameter>(), (result, array) =>
{
result.AddRange(array);
return result;
})).Try([]);
}
public static IParser<LexicalToken, SubprogramHead> 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<LexicalToken, SubprogramBody> SubprogramBodyParser()
{ {
return from constant in ConstDeclarationsParser() return from constant in ConstDeclarationsParser()
from variables in VariableDeclarationsParser() from variables in VariableDeclarationsParser()
from block in CompoundStatementParser() from block in CompoundStatementParser()
select new ProgramBody(constant.Convert<BlockNode>(), variables, block); select new SubprogramBody(constant, variables, block);
}
public static IParser<LexicalToken, Subprogram> SubprogramParser()
{
return from head in SubprogramHeadParser()
from _ in Delimiter(";")
from body in SubprogramBodyParser()
select new Subprogram(head, body);
}
public static IParser<LexicalToken, ProgramBody> 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<LexicalToken, ProgramHead> ProgramHeadParser() public static IParser<LexicalToken, ProgramHead> ProgramHeadParser()

View File

@ -0,0 +1,20 @@
using CanonSharp.Pascal.Scanner;
namespace CanonSharp.Pascal.SyntaxTree;
public sealed class ForNode(
LexicalToken identifier,
SyntaxNodeBase leftCondition,
SyntaxNodeBase rightCondition,
SyntaxNodeBase statement) : SyntaxNodeBase
{
public override SyntaxNodeType NodeType => SyntaxNodeType.For;
public LexicalToken Identifier => identifier;
public SyntaxNodeBase LeftCondition => leftCondition;
public SyntaxNodeBase RightCondition => rightCondition;
public SyntaxNodeBase Statement => statement;
}

View File

@ -0,0 +1,26 @@
namespace CanonSharp.Pascal.SyntaxTree;
public sealed class IfNode : SyntaxNodeBase
{
public override SyntaxNodeType NodeType => SyntaxNodeType.If;
public SyntaxNodeBase Condition { get; }
public SyntaxNodeBase Statement { get; }
public SyntaxNodeBase? ElseStatement { get; }
public IfNode(SyntaxNodeBase condition, SyntaxNodeBase statement)
{
Condition = condition;
Statement = statement;
ElseStatement = null;
}
public IfNode(SyntaxNodeBase condition, SyntaxNodeBase statement, SyntaxNodeBase elseStatement)
{
Condition = condition;
Statement = statement;
ElseStatement = elseStatement;
}
}

View File

@ -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;
}

View File

@ -0,0 +1,18 @@
using CanonSharp.Pascal.Scanner;
namespace CanonSharp.Pascal.SyntaxTree;
public sealed class ProcedureCallNode : SyntaxNodeBase
{
public ProcedureCallNode(LexicalToken identifier, IEnumerable<SyntaxNodeBase> parameters)
{
Identifier = identifier;
Parameters.AddRange(parameters);
}
public override SyntaxNodeType NodeType => SyntaxNodeType.ProcedureCall;
public LexicalToken Identifier { get; }
public List<SyntaxNodeBase> Parameters { get; } = [];
}

View File

@ -1,6 +1,9 @@
namespace CanonSharp.Pascal.SyntaxTree; 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; public override SyntaxNodeType NodeType => SyntaxNodeType.ProgramBody;
@ -8,5 +11,7 @@ public sealed class ProgramBody(BlockNode constantDeclarations, BlockNode variab
public BlockNode VariableDeclarations => variableDeclarations; public BlockNode VariableDeclarations => variableDeclarations;
public BlockNode Subprograms => subprograms;
public BlockNode MainBlock => mainBlock; public BlockNode MainBlock => mainBlock;
} }

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<Parameter> Parameters { get; } = [];
public SyntaxNodeBase? TypeToken { get; }
public SubprogramHead(LexicalToken identifier, IEnumerable<Parameter> parameters)
{
Identifier = identifier;
Parameters.AddRange(parameters);
TypeToken = null;
}
public SubprogramHead(LexicalToken identifier, IEnumerable<Parameter> parameters, SyntaxNodeBase typeToken) : this(
identifier, parameters)
{
TypeToken = typeToken;
}
}

View File

@ -14,6 +14,14 @@ public enum SyntaxNodeType
Block, Block,
Constant, Constant,
VariableDeclaration, VariableDeclaration,
If,
While,
For,
ProcedureCall,
Parameter,
SubprogramHead,
SubprogramBody,
SubProgram,
ProgramBody, ProgramBody,
ProgramHead, ProgramHead,
Program Program

View File

@ -0,0 +1,10 @@
namespace CanonSharp.Pascal.SyntaxTree;
public sealed class WhileNode(SyntaxNodeBase condition, SyntaxNodeBase statement) : SyntaxNodeBase
{
public override SyntaxNodeType NodeType => SyntaxNodeType.While;
public SyntaxNodeBase Condition => condition;
public SyntaxNodeBase Statement => statement;
}

View File

@ -82,6 +82,23 @@ public sealed class ExpressionParserTests : GrammarParserTestBase
Assert.Equal(1, binaryOperatorNode.Right.Convert<IntegerValueNode>().Value); Assert.Equal(1, binaryOperatorNode.Right.Convert<IntegerValueNode>().Value);
} }
[Fact]
public void ProcedureCallTest()
{
ProcedureCallNode node = RunParser<ProcedureCallNode>(GrammarParser.FactorParser(), "test(1)");
Assert.Equal("test", node.Identifier.LiteralValue);
Assert.Single(node.Parameters);
Assert.Equal(1, node.Parameters[0].Convert<IntegerValueNode>().Value);
node = RunParser<ProcedureCallNode>(GrammarParser.FactorParser(), "test()");
Assert.Equal("test", node.Identifier.LiteralValue);
Assert.Empty(node.Parameters);
VariableNode variableNode = RunParser<VariableNode>(GrammarParser.FactorParser(), "test");
Assert.Equal("test", variableNode.Identifier.LiteralValue);
Assert.Empty(variableNode.Indexers);
}
[Fact] [Fact]
public void SingleTermTest() public void SingleTermTest()
{ {

View File

@ -138,6 +138,90 @@ public class ProgramParserTests : GrammarParserTestBase
a[10,0] := 1; a[10,0] := 1;
end. end.
""")] """)]
[InlineData("""
program main;
begin
begin
end
end.
""")]
[InlineData("""
program main;
var i : integer;
begin
while i < 10 do
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) public void ProgramParseTest(string input)
{ {
ProgramParse(input); ProgramParse(input);

View File

@ -15,6 +15,114 @@ public class StatementParserTests : GrammarParserTestBase
Assert.Equal(1, node.Expression.Convert<IntegerValueNode>().Value); Assert.Equal(1, node.Expression.Convert<IntegerValueNode>().Value);
} }
[Fact]
public void IfTest1()
{
IfNode node = RunParser<IfNode>(GrammarParser.IfParser(), """
if i = 1 then
a := a + 1
else
a := a + 2
""");
Assert.NotNull(node.ElseStatement);
BinaryOperatorNode condition = node.Condition.Convert<BinaryOperatorNode>();
Assert.Equal(BinaryOperatorType.Equal, condition.OperatorType);
AssignNode statement = node.Statement.Convert<AssignNode>();
Assert.Equal("a", statement.Variable.Identifier.LiteralValue);
AssignNode elseStatement = node.Statement.Convert<AssignNode>();
Assert.Equal("a", elseStatement.Variable.Identifier.LiteralValue);
}
[Fact]
public void IfTest2()
{
IfNode node = RunParser<IfNode>(GrammarParser.IfParser(), """
if i = 1 then
if i = 2 then
a := a + 1
else
a := a + 2
""");
Assert.Null(node.ElseStatement);
IfNode subIfNode = node.Statement.Convert<IfNode>();
Assert.NotNull(subIfNode.ElseStatement);
}
[Fact]
public void ForTest1()
{
ForNode node = RunParser<ForNode>(GrammarParser.ForParser(), """
for i := 1 to 10 do
a := a + i
""");
Assert.Equal("i", node.Identifier.LiteralValue);
Assert.Equal(1, node.LeftCondition.Convert<IntegerValueNode>().Value);
Assert.Equal(10, node.RightCondition.Convert<IntegerValueNode>().Value);
AssignNode assignNode = node.Statement.Convert<AssignNode>();
Assert.Equal("a", assignNode.Variable.Identifier.LiteralValue);
}
[Fact]
public void WhileTest1()
{
WhileNode node = RunParser<WhileNode>(GrammarParser.WhileParser(), """
while c >= 1 do
a := a + 1;
""");
BinaryOperatorNode binaryOperatorNode = node.Condition.Convert<BinaryOperatorNode>();
Assert.Equal(BinaryOperatorType.GreaterEqual, binaryOperatorNode.OperatorType);
AssignNode assignNode = node.Statement.Convert<AssignNode>();
Assert.Equal("a", assignNode.Variable.Identifier.LiteralValue);
}
[Fact]
public void ProcedureCallTest1()
{
ProcedureCallNode node = RunParser<ProcedureCallNode>(GrammarParser.ProcedureCallParser(), "test()");
Assert.Equal("test", node.Identifier.LiteralValue);
Assert.Empty(node.Parameters);
node = RunParser<ProcedureCallNode>(GrammarParser.ProcedureCallParser(), "test");
Assert.Equal("test", node.Identifier.LiteralValue);
Assert.Empty(node.Parameters);
}
[Fact]
public void ProcedureCallTest2()
{
ProcedureCallNode node = RunParser<ProcedureCallNode>(GrammarParser.ProcedureCallParser(),
"test(1 + 1, true)");
Assert.Equal(2, node.Parameters.Count);
BinaryOperatorNode firstParameter = node.Parameters[0].Convert<BinaryOperatorNode>();
Assert.Equal(BinaryOperatorType.Add, firstParameter.OperatorType);
BooleanValueNode secondParameter = node.Parameters[1].Convert<BooleanValueNode>();
Assert.True(secondParameter.Value);
}
[Fact]
public void ProcedureCallTest3()
{
ProcedureCallNode node =
RunParser<ProcedureCallNode>(GrammarParser.ProcedureCallParser(), "test(1 * + 1, test2[0])");
Assert.Equal(2, node.Parameters.Count);
VariableNode secondParameter = node.Parameters[1].Convert<VariableNode>();
Assert.Equal("test2", secondParameter.Identifier.LiteralValue);
Assert.Single(secondParameter.Indexers);
}
[Theory] [Theory]
[InlineData(""" [InlineData("""
begin begin

View File

@ -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<Parameter> parameters = RunParser<Parameter>(GrammarParser.ParameterParser(), "a ,b : integer");
Assert.Equal(2, parameters.Count);
Assert.All(parameters, p =>
{
Assert.False(p.IsReference);
Assert.Equal("integer", p.TypeNode.Convert<TypeNode>().TypeToken.LiteralValue);
});
parameters = RunParser<Parameter>(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<TypeNode>().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<Parameter> parameters = RunParser<Parameter>(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<SubprogramHead>(GrammarParser.SubprogramHeadParser(), input);
Assert.Equal(count, head.Parameters.Count);
}
[Fact]
public void SubprogramHeadTest1()
{
SubprogramHead head =
RunParser<SubprogramHead>(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<TypeNode>().TypeToken.LiteralValue);
}
[Fact]
public void SubprogramBodyTest1()
{
SubprogramBody body = RunParser<SubprogramBody>(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);
}
}