diff --git a/Core/Implementation/RoslynContext.cs b/Core/Implementation/RoslynContext.cs index 7742dc9..f597b5b 100644 --- a/Core/Implementation/RoslynContext.cs +++ b/Core/Implementation/RoslynContext.cs @@ -278,30 +278,21 @@ internal void Parse(string input, string currentScriptFile, string nugetRepoIden IncludeScript(input, currentScriptFile, nugetRepoIdentifier); else if (HelpItemRegex().IsMatch(input)) HelpItem(input); - else if (ParseScriptRegex().IsMatch(input)) - ParseSingle(input); + else if (IsolatedScriptLineForSpecialFunctionsRegex().IsMatch(input)) + ParseSingleUnsafe(input); else ParseSingle(input); } - internal void ParseUnsafe(string input, string currentScriptFile, string nugetRepoIdentifier) - { - if (ImportModuleRegex().IsMatch(input)) - ImportModule(input, nugetRepoIdentifier); - else if (IncludeScriptRegex().IsMatch(input)) - IncludeScript(input, currentScriptFile, nugetRepoIdentifier); - else if (HelpItemRegex().IsMatch(input)) - HelpItem(input); - else if (ParseScriptRegex().IsMatch(input)) - ParseSingle(input); - else ParseSingleUnsafe(input); - } internal object Evaluate(string expression, string currentScriptFile, string nugetRepoIdentifier) { if (ImportModuleRegex().IsMatch(expression)) return null; else if (IncludeScriptRegex().IsMatch(expression)) return null; - else if (ParseScriptRegex().IsMatch(expression)) - return ParseSingle(expression, false); // Parse is safe when executed on its own (without side effect before or after, but with side effect to the context) + else if (IsolatedScriptLineForSpecialFunctionsRegex().IsMatch(expression)) + { + ParseSingleUnsafe(expression); // Parse is safe when executed on its own (without side effect before or after, but with side effect to the context) + return null; + } else if (HelpItemRegex().IsMatch(expression)) return null; else @@ -427,7 +418,8 @@ private void ParseSingleUnsafe(string script) { try { - State = State.ContinueWithAsync(SyntaxWrap(script)).Result; + // Just execute without modifying state + State.ContinueWithAsync(SyntaxWrap(script)); if (State.ReturnValue != null) PrintReturnValuePreviews(State.ReturnValue); } @@ -654,8 +646,8 @@ public static string PrintMethod(MethodInfo m) [GeneratedRegex(@"^Include\((.*?)(, ?(.*?))?\);?$")] public static partial Regex IncludeScriptRegex(); - [GeneratedRegex(@"^Parse\((.*?)(, ?(.*?))?\);?$")] - public static partial Regex ParseScriptRegex(); + [GeneratedRegex(@"^(Parse\((.*?)(, ?(.*?))?\);?)|(Pull\((.*?)(, ?(.*?))?\);?)$")] + public static partial Regex IsolatedScriptLineForSpecialFunctionsRegex(); [GeneratedRegex(@"^Help\((.*?)\)$")] public static partial Regex HelpItemRegex(); diff --git a/Core/Interpreter.cs b/Core/Interpreter.cs index 765d86b..c2fa5a8 100644 --- a/Core/Interpreter.cs +++ b/Core/Interpreter.cs @@ -28,7 +28,7 @@ public class Interpreter * v0.4.0: Remove `#` style line comment (potential conflict with #region due to how we parse it). * v0.5.0: Add add-on standard library CentralSnippets. Implement construct `Pull()` and `Preview()`. * v0.5.1: Enhance Roslyn Context with script record. - * v0.5.2: Implement `ParseUnsafe()`. + * v0.5.2: Implement language level `Parse()` and `Pull()` handling. """; #endregion @@ -75,8 +75,6 @@ public void Start(Action outputHandler = null) } public void Parse(string script) => Context.Parse(script, ScriptFile, NugetRepoIdentifier); - internal void ParseUnsafe(string script) - => Context.ParseUnsafe(script, ScriptFile, NugetRepoIdentifier); public object Evaluate(string expression) => Context.Evaluate(expression, ScriptFile, NugetRepoIdentifier); public string GetState() @@ -141,7 +139,7 @@ public static string[] SplitScripts(string text) if (!currentLineIsInBlockComment && (RoslynContext.ImportModuleRegex().IsMatch(line) || RoslynContext.IncludeScriptRegex().IsMatch(line) - || RoslynContext.ParseScriptRegex().IsMatch(line) // Remark-cz: This means that our `Parse()` function when executed inside a script can only be on its own line + || RoslynContext.IsolatedScriptLineForSpecialFunctionsRegex().IsMatch(line) // Remark-cz: This means that our `Parse()` function etc. when executed inside a script can only be on its own line || RoslynContext.HelpItemRegex().IsMatch(line))) { if (scriptBuilder.Length != 0) diff --git a/Core/Utilities/Construct.cs b/Core/Utilities/Construct.cs index ca684ca..7a46246 100644 --- a/Core/Utilities/Construct.cs +++ b/Core/Utilities/Construct.cs @@ -25,15 +25,6 @@ public static void Help(object instance) #region Runtime Parsing internal static Interpreter CurrentInterpreter; - /// - /// Compared to Parse, this will ignore context lock - /// - public static void ParseUnsafe(string script) - { - if (CurrentInterpreter != null) - CurrentInterpreter.ParseUnsafe(script); - else throw new ApplicationException("Interpreter is not initialized."); - } public static void Parse(string script) { if (CurrentInterpreter != null) diff --git a/StandardLibraries/CentralSnippets/Main.cs b/StandardLibraries/CentralSnippets/Main.cs index bf02c4b..b71a3b4 100644 --- a/StandardLibraries/CentralSnippets/Main.cs +++ b/StandardLibraries/CentralSnippets/Main.cs @@ -20,7 +20,7 @@ public static void Pull(string snippetIdentifier, bool disableSSL = false) string content = GetContent(SnippetsHostSite, SnippetsRootFolder, snippetIdentifier, disableSSL); // Remark-cz: We need to split script first to allow handling of specific Pure constructs (e.g. Import) foreach(var segment in Interpreter.SplitScripts(content)) - Core.Utilities.Construct.ParseUnsafe(segment); + Core.Utilities.Construct.Parse(segment); } public static void Preview(string snippetIdentifier, bool disableSSL = false) { diff --git a/UnitTests/CoreTest/ScriptParsingTest.cs b/UnitTests/CoreTest/ScriptParsingTest.cs index 9903a7b..afaa110 100644 --- a/UnitTests/CoreTest/ScriptParsingTest.cs +++ b/UnitTests/CoreTest/ScriptParsingTest.cs @@ -151,74 +151,32 @@ string Build(string markdown) Assert.Equal(expected + script.Replace("Import(Markdig)", string.Empty).Replace("\r\n", "\n"), interpreter.GetState().Replace("\r\n", "\n")); } [Fact] - public void ParsingScriptInsideInterpreterShouldNOTWork() + public void ParsingScriptInsideInterpreterShouldNOTWorkWhenSyntaxDoesntPermit() { Interpreter interpreter = new(null, null, null, null, null); interpreter.Start(); - Core.Utilities.Construct.Parse("""" - using Core; - string script = """ - Import(Markdig) - - string Build(string markdown) - { - var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); - var document = Markdown.ToHtml("This is a text with some *emphasis*", pipeline); - return document; - } - """; - var segments = Interpreter.SplitScripts(script); - // The first pass will seem to work but NOT really because state is already messed up - foreach (var a in segments) - ParseUnsafe(a); + Assert.Throws(() => + { + Core.Utilities.Construct.Parse("""" + int a = 5; Parse("WriteLine(5)"); """"); - - string expected = """" - string[] Arguments = Array.Empty(); - using Core; - string script = """ - Import(Markdig) - - string Build(string markdown) - { - var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); - var document = Markdown.ToHtml("This is a text with some *emphasis*", pipeline); - return document; - } - """; - var segments = Interpreter.SplitScripts(script); - // The first pass will seem to work but NOT really because state is already messed up - foreach (var a in segments) - ParseUnsafe(a); - """".Replace("\r\n", "\n"); - string currentState = interpreter.GetState().Replace("\r\n", "\n"); - Assert.Equal(expected, currentState); - - // Remark-cz: At this time, at the end of previous state, the namespace isn't really imported - string secondScript = """" - // This second pass will not fail but also not cause the segment to be included in the state. - // Because the `Import` construct will think the library is imported so won't import it again, - // but since the namespace isn't really imported, so it will fail during execution - foreach (var a in segments) - ParseUnsafe(a); - """"; - Core.Utilities.Construct.Parse(secondScript); - currentState = interpreter.GetState().Replace("\r\n", "\n"); - Assert.Equal((expected + Environment.NewLine + secondScript).Replace("\r\n", "\n"), currentState); + }); } [Fact] - public void ParsingScriptInsideInterpreterShouldNOTWork2() + public void ParsingScriptInsideInterpreterAsSingleLineShouldWorkOutOfBox() { Interpreter interpreter = new(null, null, null, null, null); interpreter.Start(); - Assert.Throws(() => - { - Core.Utilities.Construct.Parse("""" + Core.Utilities.Construct.Parse("""" Parse("WriteLine(5)"); """"); - }); + + Assert.Equal(""" + string[] Arguments = Array.Empty(); + WriteLine(5) + """.Replace("\r\n", "\n"), interpreter.GetState().Replace("\r\n", "\n")); } } } \ No newline at end of file