From 5800747485fc82b8be730009e3ce66a1cd711b48 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 20 Nov 2025 13:06:36 +0000
Subject: [PATCH 1/3] Initial plan
From fadc27935935413c26bcac398e83904f2ab1f6b8 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 20 Nov 2025 13:10:04 +0000
Subject: [PATCH 2/3] Initial repository exploration
Co-authored-by: richorama <353138+richorama@users.noreply.github.com>
---
test_output.txt | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 test_output.txt
diff --git a/test_output.txt b/test_output.txt
new file mode 100644
index 0000000..645bc59
--- /dev/null
+++ b/test_output.txt
@@ -0,0 +1,11 @@
+ Determining projects to restore...
+ All projects are up-to-date for restore.
+ IronBlock -> /home/runner/work/IronBlock/IronBlock/IronBlock/bin/Debug/netstandard2.0/IronBlock.dll
+ IronBlock.Tests -> /home/runner/work/IronBlock/IronBlock/IronBlock.Tests/bin/Debug/net9.0/IronBlock.Tests.dll
+Test run for /home/runner/work/IronBlock/IronBlock/IronBlock.Tests/bin/Debug/net9.0/IronBlock.Tests.dll (.NETCoreApp,Version=v9.0)
+VSTest version 18.0.1 (x64)
+
+Starting test execution, please wait...
+A total of 1 test files matched the specified pattern.
+
+Passed! - Failed: 0, Passed: 161, Skipped: 0, Total: 161, Duration: 476 ms - IronBlock.Tests.dll (net9.0)
From 5db31bfc22fc78188d1abcfbe9bc32a0fbdc1723 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 20 Nov 2025 13:15:09 +0000
Subject: [PATCH 3/3] Add break and continue support to for/foreach loops and
fix whileuntil handling
Co-authored-by: richorama <353138+richorama@users.noreply.github.com>
---
.gitignore | 1 +
IronBlock.Tests/ControlsTests.cs | 712 ++++++++++++++++++
IronBlock/Blocks/Controls/ControlsFor.cs | 14 +
IronBlock/Blocks/Controls/ControlsForEach.cs | 13 +
.../Blocks/Controls/ControlsWhileUntil.cs | 27 +-
test_output.txt | 11 -
6 files changed, 764 insertions(+), 14 deletions(-)
delete mode 100644 test_output.txt
diff --git a/.gitignore b/.gitignore
index 65181f1..73d612b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -212,3 +212,4 @@ GeneratedArtifacts/
_Pvt_Extensions/
ModelManifest.xml
/IronBlock.Runner/MyExample.xml
+test_output.txt
diff --git a/IronBlock.Tests/ControlsTests.cs b/IronBlock.Tests/ControlsTests.cs
index cabfc56..668c367 100644
--- a/IronBlock.Tests/ControlsTests.cs
+++ b/IronBlock.Tests/ControlsTests.cs
@@ -330,5 +330,717 @@ public void Test_Controls_For()
}
+ [TestMethod]
+ public void Test_Controls_For_Break()
+ {
+ const string xml = @"
+
+
+ i
+
+
+ i
+
+
+ 1
+
+
+
+
+ 5
+
+
+
+
+ 1
+
+
+
+
+
+
+ i
+
+
+
+
+
+
+ EQ
+
+
+ i
+
+
+
+
+ 3
+
+
+
+
+
+
+ BREAK
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ Assert.AreEqual("1,2,3", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
+ [TestMethod]
+ public void Test_Controls_For_Continue()
+ {
+ const string xml = @"
+
+
+ i
+
+
+ i
+
+
+ 1
+
+
+
+
+ 5
+
+
+
+
+ 1
+
+
+
+
+
+
+ EQ
+
+
+ i
+
+
+
+
+ 3
+
+
+
+
+
+
+ CONTINUE
+
+
+
+
+
+
+ i
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ Assert.AreEqual("1,2,4,5", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
+ [TestMethod]
+ public void Test_Controls_ForEach_Break()
+ {
+ const string xml = @"
+
+
+ i
+
+
+ i
+
+
+
+ SPLIT
+
+
+ a,b,c,d,e
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+ i
+
+
+
+
+
+
+ EQ
+
+
+ i
+
+
+
+
+ c
+
+
+
+
+
+
+ BREAK
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ Assert.AreEqual("a,b,c", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
+ [TestMethod]
+ public void Test_Controls_ForEach_Continue()
+ {
+ const string xml = @"
+
+
+ i
+
+
+ i
+
+
+
+ SPLIT
+
+
+ a,b,c,d,e
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+ EQ
+
+
+ i
+
+
+
+
+ c
+
+
+
+
+
+
+ CONTINUE
+
+
+
+
+
+
+ i
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ Assert.AreEqual("a,b,d,e", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
+ [TestMethod]
+ public void Test_Controls_While_Continue()
+ {
+ const string xml = @"
+
+
+ x
+
+
+ x
+
+
+ 0
+
+
+
+
+ WHILE
+
+
+ LT
+
+
+ x
+
+
+
+
+ 5
+
+
+
+
+
+
+ x
+
+
+ ADD
+
+
+ x
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+ EQ
+
+
+ x
+
+
+
+
+ 3
+
+
+
+
+
+
+ CONTINUE
+
+
+
+
+
+
+ x
+
+
+
+
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ Assert.AreEqual("1,2,4,5", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
+ [TestMethod]
+ public void Test_Controls_Until_Break()
+ {
+ const string xml = @"
+
+
+ x
+
+
+ x
+
+
+ 0
+
+
+
+
+ UNTIL
+
+
+ GT
+
+
+ x
+
+
+
+
+ 10
+
+
+
+
+
+
+ x
+
+
+ ADD
+
+
+ x
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+ x
+
+
+
+
+
+
+ EQ
+
+
+ x
+
+
+
+
+ 3
+
+
+
+
+
+
+ BREAK
+
+
+
+
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ Assert.AreEqual("1,2,3", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
+ [TestMethod]
+ public void Test_Controls_Until_Continue()
+ {
+ const string xml = @"
+
+
+ x
+
+
+ x
+
+
+ 0
+
+
+
+
+ UNTIL
+
+
+ GT
+
+
+ x
+
+
+
+
+ 5
+
+
+
+
+
+
+ x
+
+
+ ADD
+
+
+ x
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+ EQ
+
+
+ x
+
+
+
+
+ 3
+
+
+
+
+
+
+ CONTINUE
+
+
+
+
+
+
+ x
+
+
+
+
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ Assert.AreEqual("1,2,4,5,6", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
+ [TestMethod]
+ public void Test_Controls_Nested_Loops_EscapeMode_Isolation()
+ {
+ const string xml = @"
+
+
+ i
+ j
+
+
+ i
+
+
+ 1
+
+
+
+
+ 3
+
+
+
+
+ 1
+
+
+
+
+ j
+
+
+ 1
+
+
+
+
+ 3
+
+
+
+
+ 1
+
+
+
+
+
+
+ EQ
+
+
+ j
+
+
+
+
+ 2
+
+
+
+
+
+
+ BREAK
+
+
+
+
+
+
+
+
+
+ i
+
+
+
+
+ j
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ outer:
+
+
+
+
+ i
+
+
+
+
+
+
+
+
+
+
+";
+ new Parser()
+ .AddStandardBlocks()
+ .AddDebugPrinter()
+ .Parse(xml)
+ .Evaluate();
+
+ // Inner loop breaks at j=2, but outer loop continues
+ // For each i (1,2,3), inner prints j=1 only, then outer prints "outer:i"
+ Assert.AreEqual("11,outer:1,21,outer:2,31,outer:3", string.Join(",", TestExtensions.GetDebugText()));
+ }
+
+
}
}
diff --git a/IronBlock/Blocks/Controls/ControlsFor.cs b/IronBlock/Blocks/Controls/ControlsFor.cs
index 46e8085..8809fee 100644
--- a/IronBlock/Blocks/Controls/ControlsFor.cs
+++ b/IronBlock/Blocks/Controls/ControlsFor.cs
@@ -37,6 +37,20 @@ public override object Evaluate(Context context)
return context.ReturnValue;
}
statement.Evaluate(context);
+
+ if (context.EscapeMode == EscapeMode.Break)
+ {
+ context.EscapeMode = EscapeMode.None;
+ break;
+ }
+
+ if (context.EscapeMode == EscapeMode.Continue)
+ {
+ context.EscapeMode = EscapeMode.None;
+ context.Variables[variableName] = (double)context.Variables[variableName] + byValue;
+ continue;
+ }
+
if (context.EscapeMode == EscapeMode.Return)
{
return context.ReturnValue;
diff --git a/IronBlock/Blocks/Controls/ControlsForEach.cs b/IronBlock/Blocks/Controls/ControlsForEach.cs
index a99cdcf..e26897c 100644
--- a/IronBlock/Blocks/Controls/ControlsForEach.cs
+++ b/IronBlock/Blocks/Controls/ControlsForEach.cs
@@ -32,6 +32,19 @@ public override object Evaluate(Context context)
context.Variables.Add(variableName, item);
}
statement.Evaluate(context);
+
+ if (context.EscapeMode == EscapeMode.Break)
+ {
+ context.EscapeMode = EscapeMode.None;
+ break;
+ }
+
+ if (context.EscapeMode == EscapeMode.Continue)
+ {
+ context.EscapeMode = EscapeMode.None;
+ continue;
+ }
+
if (context.EscapeMode == EscapeMode.Return)
{
return context.ReturnValue;
diff --git a/IronBlock/Blocks/Controls/ControlsWhileUntil.cs b/IronBlock/Blocks/Controls/ControlsWhileUntil.cs
index 1ada31f..c6a487a 100644
--- a/IronBlock/Blocks/Controls/ControlsWhileUntil.cs
+++ b/IronBlock/Blocks/Controls/ControlsWhileUntil.cs
@@ -22,16 +22,24 @@ public override object Evaluate(Context context)
{
while ((bool)value.Evaluate(context))
{
+ if (context.EscapeMode == EscapeMode.Return)
+ {
+ return context.ReturnValue;
+ }
+ statement.Evaluate(context);
+
if (context.EscapeMode == EscapeMode.Break)
{
context.EscapeMode = EscapeMode.None;
break;
}
- if (context.EscapeMode == EscapeMode.Return)
+
+ if (context.EscapeMode == EscapeMode.Continue)
{
- return context.ReturnValue;
+ context.EscapeMode = EscapeMode.None;
+ continue;
}
- statement.Evaluate(context);
+
if (context.EscapeMode == EscapeMode.Return)
{
return context.ReturnValue;
@@ -47,6 +55,19 @@ public override object Evaluate(Context context)
return context.ReturnValue;
}
statement.Evaluate(context);
+
+ if (context.EscapeMode == EscapeMode.Break)
+ {
+ context.EscapeMode = EscapeMode.None;
+ break;
+ }
+
+ if (context.EscapeMode == EscapeMode.Continue)
+ {
+ context.EscapeMode = EscapeMode.None;
+ continue;
+ }
+
if (context.EscapeMode == EscapeMode.Return)
{
return context.ReturnValue;
diff --git a/test_output.txt b/test_output.txt
deleted file mode 100644
index 645bc59..0000000
--- a/test_output.txt
+++ /dev/null
@@ -1,11 +0,0 @@
- Determining projects to restore...
- All projects are up-to-date for restore.
- IronBlock -> /home/runner/work/IronBlock/IronBlock/IronBlock/bin/Debug/netstandard2.0/IronBlock.dll
- IronBlock.Tests -> /home/runner/work/IronBlock/IronBlock/IronBlock.Tests/bin/Debug/net9.0/IronBlock.Tests.dll
-Test run for /home/runner/work/IronBlock/IronBlock/IronBlock.Tests/bin/Debug/net9.0/IronBlock.Tests.dll (.NETCoreApp,Version=v9.0)
-VSTest version 18.0.1 (x64)
-
-Starting test execution, please wait...
-A total of 1 test files matched the specified pattern.
-
-Passed! - Failed: 0, Passed: 161, Skipped: 0, Total: 161, Duration: 476 ms - IronBlock.Tests.dll (net9.0)