From d84b254716a12e2be9f895b7bde7a54045a9e4c7 Mon Sep 17 00:00:00 2001 From: jackfiled Date: Sun, 28 Apr 2024 15:13:09 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=AF=AD=E6=B3=95=E5=88=86=E6=9E=90?= =?UTF-8?q?=E7=9A=84=E8=AF=A6=E7=BB=86=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=20(#63)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-on: https://git.rrricardo.top/PostGuard/Canon/pulls/63 --- Canon.Core/Abstractions/IGrammarParser.cs | 5 +- Canon.Core/Exceptions/GrammarException.cs | 65 +++++++++++- .../PascalGrammarFailedTests.cs | 71 +++++++++++++ .../GrammarParserTests/PascalGrammarTests.cs | 99 ++++++++++++++++--- 4 files changed, 219 insertions(+), 21 deletions(-) create mode 100644 Canon.Tests/GrammarParserTests/PascalGrammarFailedTests.cs diff --git a/Canon.Core/Abstractions/IGrammarParser.cs b/Canon.Core/Abstractions/IGrammarParser.cs index b6c8728..a86e769 100644 --- a/Canon.Core/Abstractions/IGrammarParser.cs +++ b/Canon.Core/Abstractions/IGrammarParser.cs @@ -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); } } diff --git a/Canon.Core/Exceptions/GrammarException.cs b/Canon.Core/Exceptions/GrammarException.cs index a758331..6002bae 100644 --- a/Canon.Core/Exceptions/GrammarException.cs +++ b/Canon.Core/Exceptions/GrammarException.cs @@ -1,12 +1,71 @@ +using System.Text; +using Canon.Core.Abstractions; +using Canon.Core.GrammarParser; +using Canon.Core.LexicalParser; + namespace Canon.Core.Exceptions; + /// /// 语法分析中引发的异常 /// public class GrammarException : Exception { - public GrammarException() { } + public override string Message { get; } - public GrammarException(string message) : base(message) { } + /// + /// 语法分析错误时的分析状态 + /// + public ITransformer CurrentState { get; } - public GrammarException(string message, Exception innerException) : base(message, innerException) { } + /// + /// 语法分析错误时的输入符号 + /// + 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 ListNextTerminators(ITransformer state) + { + List result = []; + + result.AddRange(state.ShiftTable.Keys); + result.AddRange(state.ReduceTable.Keys); + + return result; + } } diff --git a/Canon.Tests/GrammarParserTests/PascalGrammarFailedTests.cs b/Canon.Tests/GrammarParserTests/PascalGrammarFailedTests.cs new file mode 100644 index 0000000..3c39486 --- /dev/null +++ b/Canon.Tests/GrammarParserTests/PascalGrammarFailedTests.cs @@ -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(() => + CompilerHelpers.Analyse(program)); + + testOutputHelper.WriteLine(exception.Message); + } +} diff --git a/Canon.Tests/GrammarParserTests/PascalGrammarTests.cs b/Canon.Tests/GrammarParserTests/PascalGrammarTests.cs index 757927c..aee6035 100644 --- a/Canon.Tests/GrammarParserTests/PascalGrammarTests.cs +++ b/Canon.Tests/GrammarParserTests/PascalGrammarTests.cs @@ -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); + } }