Skip to content

Commit b3b3fcb

Browse files
committed
Manage break and continue keywords in loops + Tests for break, continue and return
1 parent cd10881 commit b3b3fcb

File tree

10 files changed

+482
-61
lines changed

10 files changed

+482
-61
lines changed

CodingSeb.ExpressionEvaluator.Tests/CodingSeb.ExpressionEvaluator.Tests.csproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,15 @@
9898
<ItemGroup>
9999
<None Include="Resources\Script0007.txt" />
100100
</ItemGroup>
101+
<ItemGroup>
102+
<None Include="Resources\Script0008.txt" />
103+
</ItemGroup>
104+
<ItemGroup>
105+
<None Include="Resources\Script0009.txt" />
106+
</ItemGroup>
107+
<ItemGroup>
108+
<None Include="Resources\Script0010.txt" />
109+
</ItemGroup>
101110
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
102111
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
103112
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

CodingSeb.ExpressionEvaluator.Tests/ExpressionEvaluatorScriptEvaluateTests.cs

Lines changed: 227 additions & 40 deletions
Large diffs are not rendered by default.

CodingSeb.ExpressionEvaluator.Tests/Resources.Designer.cs

Lines changed: 69 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodingSeb.ExpressionEvaluator.Tests/Resources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,13 @@
139139
<data name="Script0007" type="System.Resources.ResXFileRef, System.Windows.Forms">
140140
<value>resources\script0007.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
141141
</data>
142+
<data name="Script0008" type="System.Resources.ResXFileRef, System.Windows.Forms">
143+
<value>resources\script0008.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
144+
</data>
145+
<data name="Script0009" type="System.Resources.ResXFileRef, System.Windows.Forms">
146+
<value>resources\script0009.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
147+
</data>
148+
<data name="Script0010" type="System.Resources.ResXFileRef, System.Windows.Forms">
149+
<value>resources\script0010.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
150+
</data>
142151
</root>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/* Script0008 */
2+
x = [valx];
3+
4+
if(x == 0)
5+
return 2;
6+
7+
x;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* Script0009 */
2+
x = "";
3+
4+
for(i = 0; i < 10; i++)
5+
{
6+
if(i == 3)
7+
continue;
8+
9+
x += $"{i},";
10+
11+
if(i == 6)
12+
break;
13+
}
14+
15+
x;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/* Script0010 */
2+
x = "";
3+
i = -1;
4+
5+
while(i < 10)
6+
{
7+
i++;
8+
9+
if(i == 3)
10+
continue;
11+
12+
x += $"{i},";
13+
14+
if(i == 6)
15+
break;
16+
}
17+
18+
x;

CodingSeb.ExpressionEvaluator/ExpressionEvaluator.cs

Lines changed: 103 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ private enum IfBlockEvaluatedState
407407

408408
#endregion
409409

410-
#region Evaluation case sensitivity and options
410+
#region Options
411411

412412
private bool optionCaseSensitiveEvaluationActive = true;
413413

@@ -542,6 +542,14 @@ public bool OptionNewFunctionEvaluationActive
542542
/// </summary>
543543
public bool OptionScriptEvaluateFunctionActive { get; set; } = false;
544544

545+
/// <summary>
546+
/// If <c>ReturnAutomaticallyLastEvaluatedExpression</c> ScriptEvaluate return automatically the last evaluated expression if no return keyword is met.
547+
/// If <c>ReturnNull</c> return null if no return keyword is met.
548+
/// If <c>ThrowSyntaxException</c> a exception is throw if no return keyword is met.
549+
/// By default : ReturnAutomaticallyLastEvaluatedExpression;
550+
/// </summary>
551+
public OptionOnNoReturnKeywordFoundInScriptAction OptionOnNoReturnKeywordFoundInScriptAction { get; set; } = OptionOnNoReturnKeywordFoundInScriptAction.ReturnAutomaticallyLastEvaluatedExpression;
552+
545553
#endregion
546554

547555
#region Reflection flags
@@ -624,6 +632,19 @@ public ExpressionEvaluator(Dictionary<string, object> variables) : this()
624632

625633
#region Main evaluate methods (Expressions and scripts ==> public)
626634

635+
/// <summary>
636+
/// Evaluate a script (multiple expressions separated by semicolon)
637+
/// Support Assignation with [=] (for simple variable write in the Variables dictionary)
638+
/// support also if, else if, else while and for keywords
639+
/// </summary>
640+
/// <typeparam name="T">The type in which to cast the result of the expression</typeparam>
641+
/// <param name="script">the script to evaluate</param>
642+
/// <returns>The result of the last evaluated expression</returns>
643+
public T ScriptEvaluate<T>(string script)
644+
{
645+
return (T)ScriptEvaluate(script);
646+
}
647+
627648
/// <summary>
628649
/// Evaluate a script (multiple expressions separated by semicolon)
629650
/// Support Assignation with [=] (for simple variable write in the Variables dictionary)
@@ -634,14 +655,25 @@ public ExpressionEvaluator(Dictionary<string, object> variables) : this()
634655
public object ScriptEvaluate(string script)
635656
{
636657
bool isReturn = false;
658+
bool isBreak = false;
659+
bool isContinue = false;
660+
661+
object result = ScriptEvaluate(script, ref isReturn, ref isBreak, ref isContinue);
637662

638-
return ScriptEvaluate(script, ref isReturn);
663+
if (isBreak)
664+
throw new ExpressionEvaluatorSyntaxErrorException("[break] keyword executed outside a loop");
665+
else if (isContinue)
666+
throw new ExpressionEvaluatorSyntaxErrorException("[continue] keyword executed outside a loop");
667+
else
668+
return result;
639669
}
640670

641-
private object ScriptEvaluate(string script, ref bool valueReturned)
671+
private object ScriptEvaluate(string script, ref bool valueReturned, ref bool breakCalled, ref bool continueCalled)
642672
{
643673
object lastResult = null;
644674
bool isReturn = valueReturned;
675+
bool isBreak = breakCalled;
676+
bool isContinue = continueCalled;
645677
int startOfExpression = 0;
646678
IfBlockEvaluatedState ifBlockEvaluatedState = IfBlockEvaluatedState.NoBlockEvaluated;
647679
List<List<string>> ifElseStatementsList = new List<List<string>>();
@@ -655,8 +687,25 @@ object AssignationOrExpressionEval(string expression)
655687

656688
expression = expression.Trim();
657689

690+
string expressionToTest = OptionCaseSensitiveEvaluationActive ? expression : expression.ToLower();
691+
692+
if(expressionToTest.Equals("break"))
693+
{
694+
isBreak = true;
695+
return lastResult;
696+
}
697+
698+
if(expressionToTest.Equals("continue"))
699+
{
700+
isContinue = true;
701+
return lastResult;
702+
}
703+
658704
expression = returnKeywordRegex.Replace(expression, match =>
659705
{
706+
if (OptionCaseSensitiveEvaluationActive && !match.Value.StartsWith("return"))
707+
return match.Value;
708+
660709
isReturn = true;
661710
return match.Value.Contains("(") ? "(" : string.Empty;
662711
});
@@ -730,15 +779,15 @@ void ExecuteIfList()
730779
string ifScript = ifElseStatementsList.Find(statement => (bool)AssignationOrExpressionEval(statement[0]))?[1];
731780

732781
if (!string.IsNullOrEmpty(ifScript))
733-
lastResult = ScriptEvaluate(ifScript, ref isReturn);
782+
lastResult = ScriptEvaluate(ifScript, ref isReturn, ref isBreak, ref isContinue);
734783

735784
ifElseStatementsList.Clear();
736785
}
737786
}
738787

739788
int i = 0;
740789

741-
while(!isReturn && i < script.Length)
790+
while(!isReturn && !isBreak && !isContinue && i < script.Length)
742791
{
743792
Match blockKeywordsBeginingMatch;
744793
Match elseBlockKeywordsBeginingMatch = null;
@@ -830,15 +879,41 @@ void ExecuteIfList()
830879
else if (keyword.Equals("while"))
831880
{
832881
while (!isReturn && (bool)AssignationOrExpressionEval(keywordAttributes[0]))
833-
lastResult = ScriptEvaluate(subScript, ref isReturn);
882+
{
883+
lastResult = ScriptEvaluate(subScript, ref isReturn, ref isBreak, ref isContinue);
884+
885+
if(isBreak)
886+
{
887+
isBreak = false;
888+
break;
889+
}
890+
if(isContinue)
891+
{
892+
isContinue = false;
893+
continue;
894+
}
895+
}
834896
}
835897
else if (keyword.Equals("for"))
836898
{
837899
void forAction(int index)
838900
{ if (keywordAttributes.Count > index && !keywordAttributes[index].Trim().Equals(string.Empty)) AssignationOrExpressionEval(keywordAttributes[index]); }
839901

840902
for (forAction(0); !isReturn && (bool)AssignationOrExpressionEval(keywordAttributes[1]); forAction(2))
841-
lastResult = ScriptEvaluate(subScript, ref isReturn);
903+
{
904+
lastResult = ScriptEvaluate(subScript, ref isReturn, ref isBreak, ref isContinue);
905+
906+
if (isBreak)
907+
{
908+
isBreak = false;
909+
break;
910+
}
911+
if (isContinue)
912+
{
913+
isContinue = false;
914+
continue;
915+
}
916+
}
842917
}
843918
}
844919

@@ -864,14 +939,21 @@ void forAction(int index)
864939
}
865940
}
866941

867-
if (!script.Substring(startOfExpression).Trim().Equals(string.Empty) && !isReturn)
942+
if (!script.Substring(startOfExpression).Trim().Equals(string.Empty) && !isReturn && !isBreak && !isContinue)
868943
throw new ExpressionEvaluatorSyntaxErrorException("A [;] character is missing.");
869944

870945
ExecuteIfList();
871946

872947
valueReturned = isReturn;
948+
breakCalled = isBreak;
949+
continueCalled = isContinue;
873950

874-
return lastResult;
951+
if (isReturn || OptionOnNoReturnKeywordFoundInScriptAction == OptionOnNoReturnKeywordFoundInScriptAction.ReturnAutomaticallyLastEvaluatedExpression)
952+
return lastResult;
953+
else if (OptionOnNoReturnKeywordFoundInScriptAction == OptionOnNoReturnKeywordFoundInScriptAction.ReturnNull)
954+
return null;
955+
else
956+
throw new ExpressionEvaluatorSyntaxErrorException("No [return] keyword found");
875957
}
876958

877959
/// <summary>
@@ -2250,6 +2332,17 @@ public static string ManageCasing(this string text, bool isCaseSensitive)
22502332

22512333
#endregion
22522334

2335+
#region linked enums
2336+
2337+
public enum OptionOnNoReturnKeywordFoundInScriptAction
2338+
{
2339+
ReturnAutomaticallyLastEvaluatedExpression,
2340+
ReturnNull,
2341+
ThrowSyntaxException
2342+
}
2343+
2344+
#endregion
2345+
22532346
#region ExpressionEvaluator linked public classes (specific Exceptions and EventArgs)
22542347

22552348
public class ExpressionEvaluatorSyntaxErrorException : Exception
@@ -2277,7 +2370,7 @@ public ExpressionEvaluatorSecurityException(string message, Exception innerExcep
22772370
public class VariableEvaluationEventArg : EventArgs
22782371
{
22792372
/// <summary>
2280-
///
2373+
/// Constructor of the VariableEvaluationEventArg
22812374
/// </summary>
22822375
/// <param name="name">The name of the variable to Evaluate</param>
22832376
public VariableEvaluationEventArg(string name, object onInstance = null)

TryWindow/MainWindow.xaml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,17 @@
1616
IsCancel="True"
1717
IsEnabled="False"
1818
Click="CancelButton_Click" />
19-
<TextBlock x:Name="ResultTextBlock" />
20-
<TextBlock x:Name="ExecutionTimeTextBlock" />
19+
<TextBox x:Name="ResultTextBlock"
20+
IsReadOnly="True"
21+
BorderThickness="0"/>
22+
<TextBox x:Name="ExecutionTimeTextBlock"
23+
BorderThickness="0"
24+
IsReadOnly="True" />
2125
</StackPanel>
2226

2327
<Border BorderBrush="Gray" BorderThickness="1">
2428
<ae:TextEditor x:Name="ScriptTextBox"
29+
TextChanged="ScriptTextBox_TextChanged"
2530
ShowLineNumbers="True"
2631
SyntaxHighlighting="C#"
2732
HorizontalScrollBarVisibility="Auto"

0 commit comments

Comments
 (0)