feat: 语法分析的详细错误信息 (#63)

Reviewed-on: PostGuard/Canon#63
This commit is contained in:
jackfiled 2024-04-28 15:13:09 +08:00
parent 73dba8b1db
commit d84b254716
4 changed files with 219 additions and 21 deletions

View File

@ -1,4 +1,5 @@
using Canon.Core.Enums;
using Canon.Core.Exceptions;
using Canon.Core.GrammarParser;
using Canon.Core.LexicalParser;
using Canon.Core.SyntaxNodes;
@ -69,11 +70,11 @@ public interface IGrammarParser
}
else
{
throw new InvalidOperationException("Run out of token but not accept");
throw new GrammarException(stack.Peek().State);
}
}
throw new InvalidOperationException("Failed to analyse input grammar");
throw new GrammarException(stack.Peek().State, enumerator.Current);
}
}

View File

@ -1,12 +1,71 @@
using System.Text;
using Canon.Core.Abstractions;
using Canon.Core.GrammarParser;
using Canon.Core.LexicalParser;
namespace Canon.Core.Exceptions;
/// <summary>
/// 语法分析中引发的异常
/// </summary>
public class GrammarException : Exception
{
public GrammarException() { }
public override string Message { get; }
public GrammarException(string message) : base(message) { }
/// <summary>
/// 语法分析错误时的分析状态
/// </summary>
public ITransformer CurrentState { get; }
public GrammarException(string message, Exception innerException) : base(message, innerException) { }
/// <summary>
/// 语法分析错误时的输入符号
/// </summary>
public SemanticToken CurrentToken { get; }
public GrammarException(ITransformer currentState)
{
CurrentState = currentState;
CurrentToken = SemanticToken.End;
StringBuilder builder = new();
builder.Append("Except ");
foreach (TerminatorBase terminatorBase in ListNextTerminators(CurrentState))
{
builder.Append('\'').Append(terminatorBase).Append("' ");
}
Message = builder.ToString();
}
public GrammarException(ITransformer currentState, SemanticToken currentToken)
{
CurrentState = currentState;
CurrentToken = currentToken;
StringBuilder builder = new();
builder.Append("Expect ");
foreach (TerminatorBase terminatorBase in ListNextTerminators(CurrentState))
{
builder.Append('\'').Append(terminatorBase).Append("',");
}
if (!CurrentToken.Equals(SemanticToken.End))
{
builder.Append("but '").Append(CurrentToken.LiteralValue).Append("' found.");
}
Message = builder.ToString();
}
private static List<TerminatorBase> ListNextTerminators(ITransformer state)
{
List<TerminatorBase> result = [];
result.AddRange(state.ShiftTable.Keys);
result.AddRange(state.ReduceTable.Keys);
return result;
}
}

View File

@ -0,0 +1,71 @@
using Canon.Core.Exceptions;
using Canon.Tests.Utils;
using Xunit.Abstractions;
namespace Canon.Tests.GrammarParserTests;
public class PascalGrammarFailedTests(ITestOutputHelper testOutputHelper)
{
[Fact]
public void StructTest()
{
const string program = """
program main;
begin
end
""";
CatchException(program);
}
[Fact]
public void AssignTest()
{
const string program = """
program main;
begin
a := 'a';
end.
""";
CatchException(program);
}
[Fact]
public void StatementTest()
{
const string program = """
program main;
begin
if a = 1 then
doSomething;
else
doSomething;
end.
""";
CatchException(program);
}
[Fact]
public void ForTest()
{
const string program = """
program main;
begin
for a = 1 to 100 do
doSomething
end.
""";
CatchException(program);
}
private void CatchException(string program)
{
GrammarException exception = Assert.Throws<GrammarException>(() =>
CompilerHelpers.Analyse(program));
testOutputHelper.WriteLine(exception.Message);
}
}

View File

@ -50,22 +50,6 @@ public class PascalGrammarTests
Assert.Equal("exFunction", root.Head.ProgramName.LiteralValue);
}
[Fact]
public void SubprogramTest()
{
const string program = """
program main;
procedure test;
begin
end;
begin
end.
""";
ProgramStruct root = CompilerHelpers.Analyse(program);
Assert.Equal("main", root.Head.ProgramName.LiteralValue);
}
[Fact]
public void CharacterTest()
{
@ -94,4 +78,87 @@ public class PascalGrammarTests
CompilerHelpers.Analyse(program);
}
[Fact]
public void MultiplyArrayTest()
{
const string program = """
program arrayTest;
var a : array [10..100, 0..10] of integer;
begin
a[10,0] := 1;
end.
""";
CompilerHelpers.Analyse(program);
}
[Fact]
public void ProcedureTest()
{
const string program = """
program main;
procedure test;
begin
end;
begin
end.
""";
CompilerHelpers.Analyse(program);
}
[Fact]
public void FunctionTest()
{
const string program = """
program main;
function test(a, b : integer) : integer;
begin
test := 1;
end;
begin
end.
""";
CompilerHelpers.Analyse(program);
}
[Fact]
public void ForLoopTest()
{
const string program = """
program main;
var i : integer;
begin
for i := 1 to 100 do
begin
doSomething(i);
end;
end.
""";
CompilerHelpers.Analyse(program);
}
[Fact]
public void IfConditionTest()
{
const string program = """
program main;
begin
if 1 = 2 then
begin
test1;
test2 := a;
end
else
begin
doSomething;
end;
end.
""";
CompilerHelpers.Analyse(program);
}
}