feat: 语法树的访问者和类型检测访问者 (#56)

Reviewed-on: PostGuard/Canon#56
This commit is contained in:
2024-04-26 10:18:49 +08:00
parent b20c3234c5
commit 17dbcccb59
63 changed files with 2757 additions and 704 deletions

View File

@@ -0,0 +1,53 @@
using Canon.Core.Abstractions;
using Canon.Core.SemanticParser;
using Canon.Core.SyntaxNodes;
using Canon.Tests.Utils;
namespace Canon.Tests.SemanticTests;
public class ConstValueTests
{
private class ConstValueVisitor : SyntaxNodeVisitor
{
public bool Pre { get; private set; }
public bool Post { get; private set; }
public override void PreVisit(ConstValue constValue)
{
constValue.OnNumberGenerator += (_, _) =>
{
Assert.False(Pre);
Pre = true;
};
}
public override void PostVisit(ConstValue constValue)
{
constValue.OnNumberGenerator += (_, _) =>
{
Assert.False(Post);
Post = true;
};
}
}
[Fact]
public void RaiseEventTest()
{
const string program = """
program main;
const a = 1;
begin
end.
""";
ProgramStruct root = CompilerHelpers.Analyse(program);
SyntaxTreeTraveller traveller = new();
ConstValueVisitor visitor = new();
traveller.Travel(root, visitor);
Assert.True(visitor.Pre);
Assert.True(visitor.Post);
}
}

View File

@@ -0,0 +1,56 @@
using Canon.Core.Abstractions;
using Canon.Core.GrammarParser;
using Canon.Core.LexicalParser;
using Canon.Core.SemanticParser;
using Canon.Core.SyntaxNodes;
using Canon.Tests.Utils;
namespace Canon.Tests.SemanticTests;
public class SyntaxTreeTravellerTests
{
private readonly ILexer _lexer = new Lexer();
private readonly IGrammarParser _grammarParser = GeneratedGrammarParser.Instance;
private readonly SyntaxTreeTraveller _traveller = new();
[Fact]
public void TravelTest()
{
const string program = """
program main;
begin
end.
""";
SampleSyntaxTreeVisitor visitor = new();
IEnumerable<SemanticToken> tokens = _lexer.Tokenize(new StringSourceReader(program));
ProgramStruct root = _grammarParser.Analyse(tokens);
_traveller.Travel(root, visitor);
List<string> result =
[
"ProgramStruct",
"ProgramHead",
"ProgramHead",
"ProgramBody",
"SubprogramDeclarations",
"SubprogramDeclarations",
"CompoundStatement",
"StatementList",
"Statement",
"Statement",
"StatementList",
"CompoundStatement",
"ProgramBody",
"ProgramStruct"
];
string[] actual = visitor.ToString().Split('\n');
foreach ((string line, uint index) in result.WithIndex())
{
Assert.Equal(line, actual[(int)index]);
}
}
}

View File

@@ -0,0 +1,306 @@
using Canon.Core.SemanticParser;
using Canon.Core.SyntaxNodes;
using Canon.Tests.Utils;
namespace Canon.Tests.SemanticTests;
public class TypeCheckVisitorTests
{
[Fact]
public void ConstTypeTest()
{
const string program = """
program main;
const a = 1; b = 1.23; c = 'a';
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("a", out Symbol? symbol));
Assert.Equal(PascalBasicType.Integer, symbol.SymbolType);
Assert.True(visitor.SymbolTable.TryGetSymbol("b", out symbol));
Assert.Equal(PascalBasicType.Real, symbol.SymbolType);
Assert.True(visitor.SymbolTable.TryGetSymbol("c", out symbol));
Assert.Equal(PascalBasicType.Character, symbol.SymbolType);
}
[Fact]
public void SingleTypeTest()
{
const string program = """
program main;
var a : integer; b : char; c : boolean; d : real;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("a", out Symbol? symbol));
Assert.Equal(PascalBasicType.Integer, symbol.SymbolType);
Assert.True(visitor.SymbolTable.TryGetSymbol("b", out symbol));
Assert.Equal(PascalBasicType.Character, symbol.SymbolType);
Assert.True(visitor.SymbolTable.TryGetSymbol("c", out symbol));
Assert.Equal(PascalBasicType.Boolean, symbol.SymbolType);
Assert.True(visitor.SymbolTable.TryGetSymbol("d", out symbol));
Assert.Equal(PascalBasicType.Real, symbol.SymbolType);
}
[Fact]
public void MulitpleTypeTest()
{
const string program = """
program main;
var a, b, c, d : integer;
e, f, g : boolean;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
IEnumerable<string> names = ["a", "b", "c", "d"];
foreach (string name in names)
{
Assert.True(visitor.SymbolTable.TryGetSymbol(name, out Symbol? symbol));
Assert.Equal(PascalBasicType.Integer, symbol.SymbolType);
}
names = ["e", "f", "g"];
foreach (string name in names)
{
Assert.True(visitor.SymbolTable.TryGetSymbol(name, out Symbol? symbol));
Assert.Equal(PascalBasicType.Boolean, symbol.SymbolType);
}
}
[Fact]
public void ArrayTest()
{
const string program = """
program main;
var a : array [0..10] of integer;
b : array [0..10, 0..20] of integer;
c : array [100..200, 1..5] of real;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("a", out Symbol? symbol));
Assert.Equal(symbol.SymbolType, new PascalArrayType(PascalBasicType.Integer, 0, 10));
Assert.True(visitor.SymbolTable.TryGetSymbol("b", out symbol));
Assert.Equal(symbol.SymbolType,
new PascalArrayType(new PascalArrayType(PascalBasicType.Integer, 0, 20), 0, 10));
Assert.True(visitor.SymbolTable.TryGetSymbol("c", out symbol));
Assert.Equal(symbol.SymbolType, new PascalArrayType(
new PascalArrayType(PascalBasicType.Real, 1, 5), 100, 200));
}
[Fact]
public void ProcedureParameterTest()
{
const string program = """
program main;
procedure test(a, b, c : integer);
begin
end;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("test", out Symbol? symbol));
Assert.Equal(symbol.SymbolType, new PascalFunctionType([
new PascalParameterType(PascalBasicType.Integer, false),
new PascalParameterType(PascalBasicType.Integer, false),
new PascalParameterType(PascalBasicType.Integer, false)
], PascalBasicType.Void));
}
[Fact]
public void ProcedureVarParameterTest()
{
const string program = """
program main;
procedure test(var a, b, c : real);
begin
end;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("test", out Symbol? symbol));
Assert.Equal(symbol.SymbolType, new PascalFunctionType([
new PascalParameterType(PascalBasicType.Real, true),
new PascalParameterType(PascalBasicType.Real, true),
new PascalParameterType(PascalBasicType.Real, true)
], PascalBasicType.Void));
}
[Fact]
public void ProcedureBothParameterTest()
{
const string program = """
program main;
procedure test(a, b : integer; var c, d: char);
begin
end;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("test", out Symbol? symbol));
Assert.Equal(symbol.SymbolType, new PascalFunctionType([
new PascalParameterType(PascalBasicType.Integer, false),
new PascalParameterType(PascalBasicType.Integer, false),
new PascalParameterType(PascalBasicType.Character, true),
new PascalParameterType(PascalBasicType.Character, true)
], PascalBasicType.Void));
}
[Fact]
public void FunctionBothParameterTest()
{
const string program = """
program main;
function test(a, b : integer; var c, d: char) : real;
begin
end;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("test", out Symbol? symbol));
Assert.Equal(symbol.SymbolType, new PascalFunctionType([
new PascalParameterType(PascalBasicType.Integer, false),
new PascalParameterType(PascalBasicType.Integer, false),
new PascalParameterType(PascalBasicType.Character, true),
new PascalParameterType(PascalBasicType.Character, true)
], PascalBasicType.Real));
}
[Fact]
public void ProcedureAndFunctionTest()
{
const string program = """
program main;
procedure test1(a : integer; var b, c : real; d: boolean);
begin
end;
function test2(var a, b : boolean) : boolean;
begin
end;
begin
end.
""";
TypeCheckVisitor visitor = CheckType(program);
Assert.True(visitor.SymbolTable.TryGetSymbol("test1", out Symbol? symbol));
Assert.Equal(symbol.SymbolType, new PascalFunctionType([
new PascalParameterType(PascalBasicType.Integer, false),
new PascalParameterType(PascalBasicType.Real, true),
new PascalParameterType(PascalBasicType.Real, true),
new PascalParameterType(PascalBasicType.Boolean, false)
], PascalBasicType.Void));
Assert.True(visitor.SymbolTable.TryGetSymbol("test2", out symbol));
Assert.Equal(symbol.SymbolType, new PascalFunctionType([
new PascalParameterType(PascalBasicType.Boolean, true),
new PascalParameterType(PascalBasicType.Boolean, true)
], PascalBasicType.Boolean));
}
/// <summary>
/// 验证函数中的符号表是否正确
/// </summary>
private class SubprogramSymbolTableTestVisitor : TypeCheckVisitor
{
public override void PostVisit(SubprogramBody subprogramBody)
{
base.PostVisit(subprogramBody);
Assert.True(SymbolTable.TryGetSymbol("a", out Symbol? symbol));
Assert.Equal(PascalBasicType.Boolean, symbol.SymbolType);
// 递归查父符号表
Assert.True(SymbolTable.TryGetSymbol("b", out symbol));
Assert.Equal(PascalBasicType.Real, symbol.SymbolType);
Assert.True(SymbolTable.TryGetSymbol("c", out symbol));
Assert.Equal(PascalBasicType.Character, symbol.SymbolType);
Assert.True(SymbolTable.TryGetSymbol("d", out symbol));
Assert.Equal(PascalBasicType.Character, symbol.SymbolType);
}
}
[Fact]
public void SubprogramSymbolTableTest()
{
const string program = """
program main;
const a = 3;
var b, c : real;
procedure test(a : boolean);
var c, d : char;
begin
end;
begin
end.
""";
ProgramStruct root = CompilerHelpers.Analyse(program);
SubprogramSymbolTableTestVisitor visitor = new();
SyntaxTreeTraveller traveller = new();
traveller.Travel(root, visitor);
Assert.True(visitor.SymbolTable.TryGetSymbol("a", out Symbol? symbol));
Assert.Equal(PascalBasicType.Integer, symbol.SymbolType);
Assert.True(symbol.Const);
Assert.True(visitor.SymbolTable.TryGetSymbol("b", out symbol));
Assert.Equal(PascalBasicType.Real, symbol.SymbolType);
Assert.True(visitor.SymbolTable.TryGetSymbol("c", out symbol));
Assert.Equal(PascalBasicType.Real, symbol.SymbolType);
Assert.True(visitor.SymbolTable.TryGetSymbol("test", out symbol));
Assert.Equal(
new PascalFunctionType([
new PascalParameterType(PascalBasicType.Boolean, false)
], PascalBasicType.Void), symbol.SymbolType);
Assert.False(visitor.SymbolTable.TryGetSymbol("d", out symbol));
}
private static TypeCheckVisitor CheckType(string program)
{
ProgramStruct root = CompilerHelpers.Analyse(program);
TypeCheckVisitor visitor = new();
SyntaxTreeTraveller traveller = new();
traveller.Travel(root, visitor);
return visitor;
}
}