Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Implement special escape functions (through ParseSingleUnsafe) #22

Merged
merged 3 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 11 additions & 19 deletions Core/Implementation/RoslynContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
Expand Down
6 changes: 2 additions & 4 deletions Core/Interpreter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -75,8 +75,6 @@ public void Start(Action<string> 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()
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 0 additions & 9 deletions Core/Utilities/Construct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,6 @@ public static void Help(object instance)

#region Runtime Parsing
internal static Interpreter CurrentInterpreter;
/// <summary>
/// Compared to Parse, this will ignore context lock
/// </summary>
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)
Expand Down
2 changes: 1 addition & 1 deletion StandardLibraries/CentralSnippets/Main.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
68 changes: 13 additions & 55 deletions UnitTests/CoreTest/ScriptParsingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RecursiveParsingException>(() =>
{
Core.Utilities.Construct.Parse(""""
int a = 5; Parse("WriteLine(5)");
"""");

string expected = """"
string[] Arguments = Array.Empty<string>();
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<RecursiveParsingException>(() =>
{
Core.Utilities.Construct.Parse(""""
Core.Utilities.Construct.Parse(""""
Parse("WriteLine(5)");
"""");
});

Assert.Equal("""
string[] Arguments = Array.Empty<string>();
WriteLine(5)
""".Replace("\r\n", "\n"), interpreter.GetState().Replace("\r\n", "\n"));
}
}
}