diff --git a/src/MoonSharp.Interpreter/Script.cs b/src/MoonSharp.Interpreter/Script.cs index 3d41e2f8..000b803e 100755 --- a/src/MoonSharp.Interpreter/Script.cs +++ b/src/MoonSharp.Interpreter/Script.cs @@ -12,6 +12,7 @@ using MoonSharp.Interpreter.Interop; using MoonSharp.Interpreter.IO; using MoonSharp.Interpreter.Platforms; +using MoonSharp.Interpreter.Tree; using MoonSharp.Interpreter.Tree.Expressions; using MoonSharp.Interpreter.Tree.Fast_Interface; @@ -23,6 +24,32 @@ namespace MoonSharp.Interpreter /// public class Script : IScriptPrivateResource { + public enum ScriptParserMessageType + { + Error, + Warning, + Info + } + + public class ScriptParserMessage + { + public string Msg { get; set; } + private Token Token { get; set; } + public ScriptParserMessageType Type { get; set; } + + internal ScriptParserMessage(Token token) + { + Token = token; + Msg = $"unexpected symbol near '{token}'"; + } + + internal ScriptParserMessage(Token token, string msg) + { + Token = token; + Msg = msg; + } + } + /// /// The version of the MoonSharp engine /// @@ -39,6 +66,7 @@ public class Script : IScriptPrivateResource Table m_GlobalTable; IDebugger m_Debugger; Table[] m_TypeMetatables = new Table[(int)LuaTypeExtensions.MaxMetaTypes]; + internal List i_ParserMessages { get; set; } = new List(); /// /// Initializes the class. @@ -838,5 +866,7 @@ Script IScriptPrivateResource.OwnerScript { get { return this; } } + + public List ParserMessages => i_ParserMessages; } } diff --git a/src/MoonSharp.Interpreter/ScriptOptions.cs b/src/MoonSharp.Interpreter/ScriptOptions.cs index 901d3c4b..238f1a06 100644 --- a/src/MoonSharp.Interpreter/ScriptOptions.cs +++ b/src/MoonSharp.Interpreter/ScriptOptions.cs @@ -29,6 +29,12 @@ internal ScriptOptions(ScriptOptions defaults) this.CheckThreadAccess = defaults.CheckThreadAccess; } + + public enum ParserErrorModes + { + Throw, + Report + } /// /// Gets or sets the current script-loader. @@ -127,5 +133,11 @@ internal ScriptOptions(ScriptOptions defaults) /// These directions will store the RHS as a string annotation on the chunk. /// public HashSet Directives { get; set; } = new HashSet(); + + /// + /// Specifies how parser reacts to errors while parsing. + /// Options are: Throw (paring is aborted after first error), Report (errors are stashed and available in Script.ParserMessages) + /// + public ParserErrorModes ParserErrorMode { get; set; } = ParserErrorModes.Throw; } } diff --git a/src/MoonSharp.Interpreter/Tree/Statements/CompositeStatement.cs b/src/MoonSharp.Interpreter/Tree/Statements/CompositeStatement.cs index e31760b6..e3673e48 100644 --- a/src/MoonSharp.Interpreter/Tree/Statements/CompositeStatement.cs +++ b/src/MoonSharp.Interpreter/Tree/Statements/CompositeStatement.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using MoonSharp.Interpreter.Execution; @@ -14,30 +15,85 @@ class CompositeStatement : Statement { List m_Statements = new List(); - public Token EndToken; - public CompositeStatement(ScriptLoadingContext lcontext, BlockEndType endType) : base(lcontext) { while (true) { - ParseAnnotations(lcontext); - Token t = lcontext.Lexer.Current; - EndToken = lcontext.Lexer.Current; - if (t.IsEndOfBlock()) break; - if (endType == BlockEndType.CloseCurly && t.Type == TokenType.Brk_Close_Curly) break; - bool forceLast; - - Statement s = Statement.CreateStatement(lcontext, out forceLast); - m_Statements.Add(s); - EndToken = lcontext.Lexer.Current; - if (forceLast) break; + try + { + ParseAnnotations(lcontext); + Token t = lcontext.Lexer.Current; + if (t.IsEndOfBlock()) break; + if (endType == BlockEndType.CloseCurly && t.Type == TokenType.Brk_Close_Curly) break; + + Statement s = CreateStatement(lcontext, out bool forceLast); + m_Statements.Add(s); + if (forceLast) break; + } + catch (InterpreterException e) + { + if (lcontext.Script.Options.ParserErrorMode == ScriptOptions.ParserErrorModes.Report) + { + Token token = null; + if (e is SyntaxErrorException se) + { + token = se.Token; + } + + lcontext.Script.i_ParserMessages.Add(new Script.ScriptParserMessage(token, e.Message)); + Synchronize(lcontext); + + if (lcontext.Lexer.PeekNext().Type == TokenType.Eof) + { + lcontext.Lexer.Next(); + break; + } + } + else + { + throw; + } + } } // eat away all superfluos ';'s while (lcontext.Lexer.Current.Type == TokenType.SemiColon) lcontext.Lexer.Next(); } + + private void Synchronize(ScriptLoadingContext lcontext) + { + while (lcontext.Lexer.PeekNext().Type != TokenType.Eof) + { + lcontext.Lexer.Next(); + Token tkn = lcontext.Lexer.Current; + + switch (tkn.Type) + { + case TokenType.ChunkAnnotation: + case TokenType.Local: + case TokenType.Until: + case TokenType.Break: + case TokenType.Continue: + case TokenType.While: + case TokenType.For: + case TokenType.ElseIf: + case TokenType.Else: + case TokenType.Function: + case TokenType.Goto: + case TokenType.Directive: + case TokenType.Do: + case TokenType.If: + { + goto endSynchronize; + } + } + } + + endSynchronize: ; + } + public override void ResolveScope(ScriptLoadingContext lcontext) { diff --git a/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/1-common-invalid.lua b/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/1-common-invalid.lua new file mode 100644 index 00000000..46dd1292 --- /dev/null +++ b/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/1-common-invalid.lua @@ -0,0 +1,4 @@ +if ( + +x = 0 +x1 = [] \ No newline at end of file diff --git a/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/1-common-invalid.txt b/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/1-common-invalid.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/2-resync-invalid.lua b/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/2-resync-invalid.lua new file mode 100644 index 00000000..76f75a93 --- /dev/null +++ b/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/2-resync-invalid.lua @@ -0,0 +1,6 @@ +if ( + +x = 0 +x1 = [] + +local x1 = = = \ No newline at end of file diff --git a/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/2-resync-invalid.txt b/src/MoonSharp.Tests/EndToEnd/CLike/SyntaxCLike/Errors/2-resync-invalid.txt new file mode 100644 index 00000000..e69de29b diff --git a/src/MoonSharp.Tests/EndToEnd/CLikeTestRunner.cs b/src/MoonSharp.Tests/EndToEnd/CLikeTestRunner.cs index 988ca721..4173af85 100644 --- a/src/MoonSharp.Tests/EndToEnd/CLikeTestRunner.cs +++ b/src/MoonSharp.Tests/EndToEnd/CLikeTestRunner.cs @@ -16,9 +16,20 @@ static string[] GetTestCases() return files; } + + [Test, TestCaseSource(nameof(GetTestCases))] + public async Task RunThrowErros(string path) + { + await RunCore(path); + } [Test, TestCaseSource(nameof(GetTestCases))] - public async Task Run(string path) + public async Task RunReportErrors(string path) + { + await RunCore(path, true); + } + + public async Task RunCore(string path, bool reportErrors = false) { string outputPath = path.Replace(".lua", ".txt"); @@ -36,14 +47,21 @@ public async Task Run(string path) script.Options.DebugPrint = s => stdOut.AppendLine(s); script.Options.IndexTablesFrom = 0; + if (path.Contains("flaky")) + { + Assert.Inconclusive($"Test {path} marked as flaky"); + return; + } + if (path.Contains("SyntaxCLike")) { script.Options.Syntax = ScriptSyntax.CLike; } - if (path.Contains("flaky")) + if (reportErrors) { - Assert.Inconclusive($"Test {path} marked as flaky"); + script.Options.ParserErrorMode = ScriptOptions.ParserErrorModes.Report; + await script.DoStringAsync(code); return; }