From 3f651dc70b6b575c3cbbd01790422398ba86d336 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Nov 2024 20:49:59 +0100
Subject: [PATCH 001/259] Set version to 0.13.0

---
 CMakeLists.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 29f589fa..5c9f64fe 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 cmake_minimum_required(VERSION 3.14)
 
-project(libscratchcpp VERSION 0.12.0 LANGUAGES C CXX)
+project(libscratchcpp VERSION 0.13.0 LANGUAGES C CXX)
 
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
 set(CMAKE_CXX_STANDARD 17)

From c36984b8a5313039757bd85ea23c2f242182ca46 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 5 Nov 2024 10:38:49 +0100
Subject: [PATCH 002/259] Optimize value_mod()

---
 src/scratch/value_functions.cpp | 14 +++-----------
 1 file changed, 3 insertions(+), 11 deletions(-)

diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp
index b35092d0..710facd5 100644
--- a/src/scratch/value_functions.cpp
+++ b/src/scratch/value_functions.cpp
@@ -354,17 +354,9 @@ extern "C"
     /*! Calculates the modulo the given values and writes the result to dst. */
     void value_mod(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2, ValueData *dst)
     {
-        double a = value_toDouble(v1);
-        double b = value_toDouble(v2);
-
-        if ((b == 0) || std::isinf(a))
-            value_assign_double(dst, std::numeric_limits<double>::quiet_NaN());
-        else if (std::isinf(b))
-            value_assign_double(dst, value_toDouble(v1));
-        else if (value_isNegative(v1) || value_isNegative(v2))
-            value_assign_double(dst, fmod(value_toDouble(v2) + fmod(value_toDouble(v1), -value_toDouble(v2)), value_toDouble(v2)));
-        else
-            value_assign_double(dst, fmod(value_toDouble(v1), value_toDouble(v2)));
+        const double a = value_toDouble(v1);
+        const double b = value_toDouble(v2);
+        value_assign_double(dst, fmod(a, b) / b < 0.0 ? fmod(a, b) + b : fmod(a, b));
     }
 
     /* comparison */

From 6b1c7642154c2304acaa7c7161c6b11f8ae22b83 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 5 Nov 2024 10:40:48 +0100
Subject: [PATCH 003/259] Use results from Value in LLVMCodeBuilder test

---
 test/dev/llvm/llvmcodebuilder_test.cpp | 150 ++++++++++++-------------
 1 file changed, 75 insertions(+), 75 deletions(-)

diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 6715cc0e..14df8254 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -226,7 +226,7 @@ TEST_F(LLVMCodeBuilderTest, Add)
 {
     std::string expected;
 
-    auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) {
+    auto addOpTest = [this, &expected](Value v1, Value v2) {
         createBuilder(true);
 
         m_builder->addConstValue(v1);
@@ -241,7 +241,7 @@ TEST_F(LLVMCodeBuilderTest, Add)
         m_builder->createAdd();
         m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
 
-        std::string str = Value(expectedResult).toString() + '\n';
+        std::string str = (v1 + v2).toString() + '\n';
         std::string expected = str + str;
 
         auto code = m_builder->finalize();
@@ -254,25 +254,25 @@ TEST_F(LLVMCodeBuilderTest, Add)
         ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
     };
 
-    addOpTest(50, 25, 75);
-    addOpTest(-500, 25, -475);
-    addOpTest(-500, -25, -525);
-    addOpTest("2.54", "6.28", 8.82);
-    addOpTest(2.54, "-6.28", -3.74);
-    addOpTest(true, true, 2);
-    addOpTest("Infinity", "Infinity", std::numeric_limits<double>::infinity());
-    addOpTest("Infinity", "-Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest("-Infinity", "Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest("-Infinity", "-Infinity", -std::numeric_limits<double>::infinity());
-    addOpTest(1, "NaN", 1);
-    addOpTest("NaN", 1, 1);
+    addOpTest(50, 25);
+    addOpTest(-500, 25);
+    addOpTest(-500, -25);
+    addOpTest("2.54", "6.28");
+    addOpTest(2.54, "-6.28");
+    addOpTest(true, true);
+    addOpTest("Infinity", "Infinity");
+    addOpTest("Infinity", "-Infinity");
+    addOpTest("-Infinity", "Infinity");
+    addOpTest("-Infinity", "-Infinity");
+    addOpTest(1, "NaN");
+    addOpTest("NaN", 1);
 }
 
 TEST_F(LLVMCodeBuilderTest, Subtract)
 {
     std::string expected;
 
-    auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) {
+    auto addOpTest = [this, &expected](Value v1, Value v2) {
         createBuilder(true);
 
         m_builder->addConstValue(v1);
@@ -287,7 +287,7 @@ TEST_F(LLVMCodeBuilderTest, Subtract)
         m_builder->createSub();
         m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
 
-        std::string str = Value(expectedResult).toString() + '\n';
+        std::string str = (v1 - v2).toString() + '\n';
         std::string expected = str + str;
 
         auto code = m_builder->finalize();
@@ -300,25 +300,25 @@ TEST_F(LLVMCodeBuilderTest, Subtract)
         ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
     };
 
-    addOpTest(50, 25, 25);
-    addOpTest(-500, 25, -525);
-    addOpTest(-500, -25, -475);
-    addOpTest("2.54", "6.28", -3.74);
-    addOpTest(2.54, "-6.28", 8.82);
-    addOpTest(true, true, 0);
-    addOpTest("Infinity", "Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest("Infinity", "-Infinity", std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", "Infinity", -std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", "-Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest(1, "NaN", 1);
-    addOpTest("NaN", 1, -1);
+    addOpTest(50, 25);
+    addOpTest(-500, 25);
+    addOpTest(-500, -25);
+    addOpTest("2.54", "6.28");
+    addOpTest(2.54, "-6.28");
+    addOpTest(true, true);
+    addOpTest("Infinity", "Infinity");
+    addOpTest("Infinity", "-Infinity");
+    addOpTest("-Infinity", "Infinity");
+    addOpTest("-Infinity", "-Infinity");
+    addOpTest(1, "NaN");
+    addOpTest("NaN", 1);
 }
 
 TEST_F(LLVMCodeBuilderTest, Multiply)
 {
     std::string expected;
 
-    auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) {
+    auto addOpTest = [this, &expected](Value v1, Value v2) {
         createBuilder(true);
 
         m_builder->addConstValue(v1);
@@ -333,7 +333,7 @@ TEST_F(LLVMCodeBuilderTest, Multiply)
         m_builder->createMul();
         m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
 
-        std::string str = Value(expectedResult).toString() + '\n';
+        std::string str = (v1 * v2).toString() + '\n';
         std::string expected = str + str;
 
         auto code = m_builder->finalize();
@@ -346,30 +346,30 @@ TEST_F(LLVMCodeBuilderTest, Multiply)
         ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
     };
 
-    addOpTest(50, 2, 100);
-    addOpTest(-500, 25, -12500);
-    addOpTest("-500", -25, 12500);
-    addOpTest("2.54", "6.28", 15.9512);
-    addOpTest(true, true, 1);
-    addOpTest("Infinity", "Infinity", std::numeric_limits<double>::infinity());
-    addOpTest("Infinity", 0, std::numeric_limits<double>::quiet_NaN());
-    addOpTest("Infinity", 2, std::numeric_limits<double>::infinity());
-    addOpTest("Infinity", -2, -std::numeric_limits<double>::infinity());
-    addOpTest("Infinity", "-Infinity", -std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", "Infinity", -std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", 0, std::numeric_limits<double>::quiet_NaN());
-    addOpTest("-Infinity", 2, -std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", -2, std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", "-Infinity", std::numeric_limits<double>::infinity());
-    addOpTest(1, "NaN", 0);
-    addOpTest("NaN", 1, 0);
+    addOpTest(50, 2);
+    addOpTest(-500, 25);
+    addOpTest("-500", -25);
+    addOpTest("2.54", "6.28");
+    addOpTest(true, true);
+    addOpTest("Infinity", "Infinity");
+    addOpTest("Infinity", 0);
+    addOpTest("Infinity", 2);
+    addOpTest("Infinity", -2);
+    addOpTest("Infinity", "-Infinity");
+    addOpTest("-Infinity", "Infinity");
+    addOpTest("-Infinity", 0);
+    addOpTest("-Infinity", 2);
+    addOpTest("-Infinity", -2);
+    addOpTest("-Infinity", "-Infinity");
+    addOpTest(1, "NaN");
+    addOpTest("NaN", 1);
 }
 
 TEST_F(LLVMCodeBuilderTest, Divide)
 {
     std::string expected;
 
-    auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) {
+    auto addOpTest = [this, &expected](Value v1, Value v2) {
         createBuilder(true);
 
         m_builder->addConstValue(v1);
@@ -384,7 +384,7 @@ TEST_F(LLVMCodeBuilderTest, Divide)
         m_builder->createDiv();
         m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
 
-        std::string str = Value(expectedResult).toString() + '\n';
+        std::string str = (v1 / v2).toString() + '\n';
         std::string expected = str + str;
 
         auto code = m_builder->finalize();
@@ -397,32 +397,32 @@ TEST_F(LLVMCodeBuilderTest, Divide)
         ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
     };
 
-    addOpTest(50, 2, 25);
-    addOpTest(-500, 25, -20);
-    addOpTest("-500", -25, 20);
-    addOpTest("3.5", "2.5", 1.4);
-    addOpTest(true, true, 1);
-    addOpTest("Infinity", "Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest("Infinity", 0, std::numeric_limits<double>::infinity());
-    addOpTest("Infinity", 2, std::numeric_limits<double>::infinity());
-    addOpTest("Infinity", -2, -std::numeric_limits<double>::infinity());
-    addOpTest("Infinity", "-Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest("-Infinity", "Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest("-Infinity", 0, -std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", 2, -std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", -2, std::numeric_limits<double>::infinity());
-    addOpTest("-Infinity", "-Infinity", std::numeric_limits<double>::quiet_NaN());
-    addOpTest(0, "Infinity", 0);
-    addOpTest(2, "Infinity", 0);
-    addOpTest(-2, "Infinity", 0);
-    addOpTest(0, "-Infinity", 0);
-    addOpTest(2, "-Infinity", 0);
-    addOpTest(-2, "-Infinity", 0);
-    addOpTest(1, "NaN", std::numeric_limits<double>::infinity());
-    addOpTest("NaN", 1, 0);
-    addOpTest(5, 0, std::numeric_limits<double>::infinity());
-    addOpTest(-5, 0, -std::numeric_limits<double>::infinity());
-    addOpTest(0, 0, std::numeric_limits<double>::quiet_NaN());
+    addOpTest(50, 2);
+    addOpTest(-500, 25);
+    addOpTest("-500", -25);
+    addOpTest("3.5", "2.5");
+    addOpTest(true, true);
+    addOpTest("Infinity", "Infinity");
+    addOpTest("Infinity", 0);
+    addOpTest("Infinity", 2);
+    addOpTest("Infinity", -2);
+    addOpTest("Infinity", "-Infinity");
+    addOpTest("-Infinity", "Infinity");
+    addOpTest("-Infinity", 0);
+    addOpTest("-Infinity", 2);
+    addOpTest("-Infinity", -2);
+    addOpTest("-Infinity", "-Infinity");
+    addOpTest(0, "Infinity");
+    addOpTest(2, "Infinity");
+    addOpTest(-2, "Infinity");
+    addOpTest(0, "-Infinity");
+    addOpTest(2, "-Infinity");
+    addOpTest(-2, "-Infinity");
+    addOpTest(1, "NaN");
+    addOpTest("NaN", 1);
+    addOpTest(5, 0);
+    addOpTest(-5, 0);
+    addOpTest(0, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, EqualComparison)

From 25bc8fce1ad855a5d10bd11d6050242e76a6b1a2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 5 Nov 2024 10:42:08 +0100
Subject: [PATCH 004/259] LLVMCodeBuilder: Implement mod operator

---
 src/dev/engine/internal/icodebuilder.h      |  2 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 19 +++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  3 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 61 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  2 +
 5 files changed, 87 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index f5852098..0d22c7e2 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -37,6 +37,8 @@ class ICodeBuilder
         virtual void createOr() = 0;
         virtual void createNot() = 0;
 
+        virtual void createMod() = 0;
+
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
         virtual void endIf() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 22f3d552..9dd2411f 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -180,6 +180,20 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Mod: {
+                assert(step.args.size() == 2);
+                const auto &arg1 = step.args[0];
+                const auto &arg2 = step.args[1];
+                // rem(a, b) / b < 0.0 ? rem(a, b) + b : rem(a, b)
+                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
+                llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first));
+                llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first));
+                llvm::Value *value = m_builder.CreateFRem(num1, num2);                                // rem(a, b)
+                llvm::Value *cond = m_builder.CreateFCmpOLT(m_builder.CreateFDiv(value, num2), zero); // rem(a, b) / b < 0.0                                                            // rem(a, b)
+                step.functionReturnReg->value = m_builder.CreateSelect(cond, m_builder.CreateFAdd(value, num2), value);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -548,6 +562,11 @@ void LLVMCodeBuilder::createNot()
     createOp(Step::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 1);
 }
 
+void LLVMCodeBuilder::createMod()
+{
+    createOp(Step::Type::Mod, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 5fb17df5..184acf90 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -39,6 +39,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createOr() override;
         void createNot() override;
 
+        void createMod() override;
+
         void beginIfStatement() override;
         void beginElseBranch() override;
         void endIf() override;
@@ -81,6 +83,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     And,
                     Or,
                     Not,
+                    Mod,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 14df8254..426f056c 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1024,6 +1024,67 @@ TEST_F(LLVMCodeBuilderTest, Not)
     addOpTest("nan");
 }
 
+TEST_F(LLVMCodeBuilderTest, Mod)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, Value v2) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->addConstValue(v2);
+        m_builder->createMod();
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->addConstValue(v2);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createMod();
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+        std::string str = (v1 % v2).toString() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        const std::string quotes2 = v2.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
+    };
+
+    addOpTest(4, 3);
+    addOpTest(3, 3);
+    addOpTest(2, 3);
+    addOpTest(1, 3);
+    addOpTest(0, 3);
+    addOpTest(-1, 3);
+    addOpTest(-2, 3);
+    addOpTest(-3, 3);
+    addOpTest(-4, 3);
+    addOpTest(4.75, 2);
+    addOpTest(-4.75, 2);
+    addOpTest(-4.75, -2);
+    addOpTest(4.75, -2);
+    addOpTest(5, 0);
+    addOpTest(-5, 0);
+    addOpTest(-2.5, "Infinity");
+    addOpTest(-1.2, "-Infinity");
+    addOpTest(2.5, "Infinity");
+    addOpTest(1.2, "-Infinity");
+    addOpTest("Infinity", 2);
+    addOpTest("-Infinity", 2);
+    addOpTest("Infinity", -2);
+    addOpTest("-Infinity", -2);
+    addOpTest(3, "NaN");
+    addOpTest(-3, "NaN");
+    addOpTest("NaN", 5);
+    addOpTest("NaN", -5);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 3271451d..92e5f3b6 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -27,6 +27,8 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createOr, (), (override));
         MOCK_METHOD(void, createNot, (), (override));
 
+        MOCK_METHOD(void, createMod, (), (override));
+
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));
         MOCK_METHOD(void, endIf, (), (override));

From 5e1e3b62ae2e5198da2ca8f7d32890abbdf75b91 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 5 Nov 2024 21:56:05 +0100
Subject: [PATCH 005/259] Fix negative zero string conversion

---
 src/scratch/value_functions_p.h     |  4 ++--
 test/scratch_classes/value_test.cpp | 14 ++++++++++++++
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h
index ca497e99..01958ab5 100644
--- a/src/scratch/value_functions_p.h
+++ b/src/scratch/value_functions_p.h
@@ -50,7 +50,7 @@ extern "C"
         return v < 0 && std::isinf(v);
     }
 
-    inline long value_convert_int_str(const char *str, int n, bool *ok)
+    inline double value_convert_int_str(const char *str, int n, bool *ok)
     {
         if (ok)
             *ok = false;
@@ -80,7 +80,7 @@ extern "C"
             *ok = true;
 
         if (isNegative)
-            return -ret;
+            return -static_cast<double>(ret); // for negative zero
         else
             return ret;
     }
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 359bc946..04cce470 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -938,6 +938,15 @@ TEST(ValueTest, ToDouble)
     v = "-0.15";
     ASSERT_EQ(v.toDouble(), -0.15);
 
+    v = "0";
+    ASSERT_EQ(v.toDouble(), 0.0);
+    v = "-0";
+    ASSERT_EQ(v.toDouble(), -0.0);
+    v = "0.0";
+    ASSERT_EQ(v.toDouble(), 0.0);
+    v = "-0.0";
+    ASSERT_EQ(v.toDouble(), -0.0);
+
     v = "+.15";
     ASSERT_EQ(v.toDouble(), 0.15);
     v = ".15";
@@ -2879,6 +2888,11 @@ TEST(ValueTest, StringToDouble)
     ASSERT_EQ(value_stringToDouble("0.15"), 0.15);
     ASSERT_EQ(value_stringToDouble("-0.15"), -0.15);
 
+    ASSERT_EQ(value_stringToDouble("0"), 0.0);
+    ASSERT_EQ(value_stringToDouble("-0"), -0.0);
+    ASSERT_EQ(value_stringToDouble("0.0"), 0.0);
+    ASSERT_EQ(value_stringToDouble("-0.0"), -0.0);
+
     ASSERT_EQ(value_stringToDouble("+.15"), 0.15);
     ASSERT_EQ(value_stringToDouble(".15"), 0.15);
     ASSERT_EQ(value_stringToDouble("-.15"), -0.15);

From ba6a97bc42acb7074fc292b06b83a591b71f32ec Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 5 Nov 2024 21:56:43 +0100
Subject: [PATCH 006/259] LLVMCodeBuilder: Implement round operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 22 ++++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 48 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 74 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 0d22c7e2..5389c159 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -38,6 +38,7 @@ class ICodeBuilder
         virtual void createNot() = 0;
 
         virtual void createMod() = 0;
+        virtual void createRound() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 9dd2411f..126c865f 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -194,6 +194,23 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Round: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // x >= 0.0 ? round(x) : (x >= -0.5 ? -0.0 : floor(x + 0.5))
+                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
+                llvm::Constant *negativeZero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(-0.0));
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::floor, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                llvm::Value *notNegative = m_builder.CreateFCmpOGE(num, zero);                                                                             // num >= 0.0
+                llvm::Value *roundNum = m_builder.CreateCall(roundFunc, num);                                                                              // round(num)
+                llvm::Value *negativeCond = m_builder.CreateFCmpOGE(num, llvm::ConstantFP::get(m_ctx, llvm::APFloat(-0.5)));                               // num >= -0.5
+                llvm::Value *negativeRound = m_builder.CreateCall(floorFunc, m_builder.CreateFAdd(num, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.5)))); // floor(x + 0.5)
+                step.functionReturnReg->value = m_builder.CreateSelect(notNegative, roundNum, m_builder.CreateSelect(negativeCond, negativeZero, negativeRound));
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -567,6 +584,11 @@ void LLVMCodeBuilder::createMod()
     createOp(Step::Type::Mod, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
 }
 
+void LLVMCodeBuilder::createRound()
+{
+    createOp(Step::Type::Round, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 184acf90..4a8c067b 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -40,6 +40,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createNot() override;
 
         void createMod() override;
+        void createRound() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -84,6 +85,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Or,
                     Not,
                     Mod,
+                    Round,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 426f056c..cb787991 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1085,6 +1085,54 @@ TEST_F(LLVMCodeBuilderTest, Mod)
     addOpTest("NaN", -5);
 }
 
+TEST_F(LLVMCodeBuilderTest, Round)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createRound();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createRound();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(4.0, 4.0);
+    addOpTest(3.2, 3.0);
+    addOpTest(3.5, 4.0);
+    addOpTest(3.6, 4.0);
+    addOpTest(-2.4, -2.0);
+    addOpTest(-2.5, -2.0);
+    addOpTest(-2.6, -3.0);
+    addOpTest(-0.4, -0.0);
+    addOpTest(-0.5, -0.0);
+    addOpTest(-0.51, -1.0);
+    addOpTest(inf, inf);
+    addOpTest(-inf, -inf);
+    addOpTest(nan, 0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 92e5f3b6..00a6adac 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -28,6 +28,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createNot, (), (override));
 
         MOCK_METHOD(void, createMod, (), (override));
+        MOCK_METHOD(void, createRound, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 7e7f7da99fcf2b9296bf4015d04e700b64cf6d98 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 6 Nov 2024 09:00:48 +0100
Subject: [PATCH 007/259] LLVMCodeBuilder: Implement abs operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 14 +++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 45 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 63 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 5389c159..1e7cb522 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -39,6 +39,7 @@ class ICodeBuilder
 
         virtual void createMod() = 0;
         virtual void createRound() = 0;
+        virtual void createAbs() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 126c865f..c6d43201 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -211,6 +211,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Abs: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                llvm::Function *absFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::fabs, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateCall(absFunc, num);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -589,6 +598,11 @@ void LLVMCodeBuilder::createRound()
     createOp(Step::Type::Round, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createAbs()
+{
+    createOp(Step::Type::Abs, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 4a8c067b..b9d4cf6f 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -41,6 +41,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void createMod() override;
         void createRound() override;
+        void createAbs() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -86,6 +87,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Not,
                     Mod,
                     Round,
+                    Abs,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index cb787991..952a8c13 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1133,6 +1133,51 @@ TEST_F(LLVMCodeBuilderTest, Round)
     addOpTest(nan, 0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Abs)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createAbs();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createAbs();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(4.0, 4.0);
+    addOpTest(3.2, 3.2);
+    addOpTest(-2.0, 2.0);
+    addOpTest(-2.5, 2.5);
+    addOpTest(-2.6, 2.6);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, 0.0);
+    addOpTest(inf, inf);
+    addOpTest(-inf, inf);
+    addOpTest(nan, 0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 00a6adac..a9a9394a 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -29,6 +29,7 @@ class CodeBuilderMock : public ICodeBuilder
 
         MOCK_METHOD(void, createMod, (), (override));
         MOCK_METHOD(void, createRound, (), (override));
+        MOCK_METHOD(void, createAbs, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 6e66240843223dfd9360f10d62a891b991c82ca2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 6 Nov 2024 14:19:27 +0100
Subject: [PATCH 008/259] LLVMCodeBuilder: Implement floor operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 14 ++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 50 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 68 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 1e7cb522..4256e497 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -40,6 +40,7 @@ class ICodeBuilder
         virtual void createMod() = 0;
         virtual void createRound() = 0;
         virtual void createAbs() = 0;
+        virtual void createFloor() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index c6d43201..51bc46c2 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -220,6 +220,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Floor: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::floor, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateCall(floorFunc, num);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -603,6 +612,11 @@ void LLVMCodeBuilder::createAbs()
     createOp(Step::Type::Abs, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createFloor()
+{
+    createOp(Step::Type::Floor, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index b9d4cf6f..0db9c8e5 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -42,6 +42,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createMod() override;
         void createRound() override;
         void createAbs() override;
+        void createFloor() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -88,6 +89,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Mod,
                     Round,
                     Abs,
+                    Floor,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 952a8c13..c69c8da4 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1178,6 +1178,56 @@ TEST_F(LLVMCodeBuilderTest, Abs)
     addOpTest(nan, 0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Floor)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createFloor();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createFloor();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(4.0, 4.0);
+    addOpTest(3.2, 3.0);
+    addOpTest(3.5, 3.0);
+    addOpTest(3.6, 3.0);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, -0.0);
+    addOpTest(-2.4, -3.0);
+    addOpTest(-2.5, -3.0);
+    addOpTest(-2.6, -3.0);
+    addOpTest(-0.4, -1.0);
+    addOpTest(-0.5, -1.0);
+    addOpTest(-0.51, -1.0);
+    addOpTest(inf, inf);
+    addOpTest(-inf, -inf);
+    addOpTest(nan, 0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index a9a9394a..64c8e99b 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -30,6 +30,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createMod, (), (override));
         MOCK_METHOD(void, createRound, (), (override));
         MOCK_METHOD(void, createAbs, (), (override));
+        MOCK_METHOD(void, createFloor, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From e01d6fff5b0b332dc9bbcb823fa6c66e8e5a50aa Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 6 Nov 2024 14:43:19 +0100
Subject: [PATCH 009/259] LLVMCodeBuilder: Implement ceil operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 14 ++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 51 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 69 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 4256e497..eb16e382 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -41,6 +41,7 @@ class ICodeBuilder
         virtual void createRound() = 0;
         virtual void createAbs() = 0;
         virtual void createFloor() = 0;
+        virtual void createCeil() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 51bc46c2..3ce2c386 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -229,6 +229,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Ceil: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                llvm::Function *ceilFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::ceil, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateCall(ceilFunc, num);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -617,6 +626,11 @@ void LLVMCodeBuilder::createFloor()
     createOp(Step::Type::Floor, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createCeil()
+{
+    createOp(Step::Type::Ceil, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 0db9c8e5..71519b58 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -43,6 +43,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createRound() override;
         void createAbs() override;
         void createFloor() override;
+        void createCeil() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -90,6 +91,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Round,
                     Abs,
                     Floor,
+                    Ceil,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index c69c8da4..212a29a1 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1228,6 +1228,57 @@ TEST_F(LLVMCodeBuilderTest, Floor)
     addOpTest(nan, 0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Ceil)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createCeil();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createCeil();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(8.0, 8.0);
+    addOpTest(3.2, 4.0);
+    addOpTest(3.5, 4.0);
+    addOpTest(3.6, 4.0);
+    addOpTest(0.4, 1.0);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, -0.0);
+    addOpTest(-2.4, -2.0);
+    addOpTest(-2.5, -2.0);
+    addOpTest(-2.6, -2.0);
+    addOpTest(-0.4, -0.0);
+    addOpTest(-0.5, -0.0);
+    addOpTest(-0.51, -0.0);
+    addOpTest(inf, inf);
+    addOpTest(-inf, -inf);
+    addOpTest(nan, 0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 64c8e99b..e5599b8d 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -31,6 +31,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createRound, (), (override));
         MOCK_METHOD(void, createAbs, (), (override));
         MOCK_METHOD(void, createFloor, (), (override));
+        MOCK_METHOD(void, createCeil, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 34d5c4268a79cfc4f6a69eb86e301e17820ac048 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 6 Nov 2024 15:04:13 +0100
Subject: [PATCH 010/259] LLVMCodeBuilder: Implement sqrt operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 17 ++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 43 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 64 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index eb16e382..7f3cd34e 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -42,6 +42,7 @@ class ICodeBuilder
         virtual void createAbs() = 0;
         virtual void createFloor() = 0;
         virtual void createCeil() = 0;
+        virtual void createSqrt() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 3ce2c386..ccf39e31 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -238,6 +238,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Sqrt: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // sqrt(x) + 0.0
+                // This avoids negative zero
+                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
+                llvm::Function *sqrtFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::sqrt, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateCall(sqrtFunc, num), zero);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -631,6 +643,11 @@ void LLVMCodeBuilder::createCeil()
     createOp(Step::Type::Ceil, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createSqrt()
+{
+    createOp(Step::Type::Sqrt, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 71519b58..7d668aa1 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -44,6 +44,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createAbs() override;
         void createFloor() override;
         void createCeil() override;
+        void createSqrt() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -92,6 +93,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Abs,
                     Floor,
                     Ceil,
+                    Sqrt,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 212a29a1..bc8f91f6 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1279,6 +1279,49 @@ TEST_F(LLVMCodeBuilderTest, Ceil)
     addOpTest(nan, 0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Sqrt)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createSqrt();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createSqrt();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(16.0, 4.0);
+    addOpTest(0.04, 0.2);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, 0.0);
+    addOpTest(-4.0, -nan); // negative NaN shouldn't be a problem
+    addOpTest(inf, inf);
+    addOpTest(-inf, -nan);
+    addOpTest(nan, 0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index e5599b8d..5f7594de 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -32,6 +32,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createAbs, (), (override));
         MOCK_METHOD(void, createFloor, (), (override));
         MOCK_METHOD(void, createCeil, (), (override));
+        MOCK_METHOD(void, createSqrt, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From a042b64c0726a9f49174522d6422e49182746301 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 6 Nov 2024 16:59:25 +0100
Subject: [PATCH 011/259] LLVMCodeBuilder: Implement sin operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 23 ++++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 47 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 74 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 7f3cd34e..ad9769af 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -43,6 +43,7 @@ class ICodeBuilder
         virtual void createFloor() = 0;
         virtual void createCeil() = 0;
         virtual void createSqrt() = 0;
+        virtual void createSin() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index ccf39e31..ec448824 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -250,6 +250,24 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Sin: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // round(sin(x * pi / 180.0) * 1e10) / 1e10 + 0.0
+                // +0.0 to avoid -0.0
+                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
+                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
+                llvm::Constant *factor = llvm::ConstantFP::get(m_ctx, llvm::APFloat(1e10));
+                llvm::Function *sinFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::sin, m_builder.getDoubleTy());
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                llvm::Value *sinResult = m_builder.CreateCall(sinFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // sin(x * pi / 180)
+                llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(sinResult, factor));                    // round(sin(x * 180) * 1e10)
+                step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateFDiv(rounded, factor), zero);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -648,6 +666,11 @@ void LLVMCodeBuilder::createSqrt()
     createOp(Step::Type::Sqrt, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createSin()
+{
+    createOp(Step::Type::Sin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 7d668aa1..2001a321 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -45,6 +45,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createFloor() override;
         void createCeil() override;
         void createSqrt() override;
+        void createSin() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -94,6 +95,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Floor,
                     Ceil,
                     Sqrt,
+                    Sin,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index bc8f91f6..7816db8e 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1322,6 +1322,53 @@ TEST_F(LLVMCodeBuilderTest, Sqrt)
     addOpTest(nan, 0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Sin)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createSin();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createSin();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(30.0, 0.5);
+    addOpTest(90.0, 1.0);
+    addOpTest(2.8e-9, 0.0);
+    addOpTest(2.9e-9, 1e-10);
+    addOpTest(570.0, -0.5);
+    addOpTest(-30.0, -0.5);
+    addOpTest(-90.0, -1.0);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, 0.0);
+    addOpTest(inf, -nan); // negative NaN shouldn't be a problem
+    addOpTest(-inf, -nan);
+    addOpTest(nan, 0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 5f7594de..a3202d3c 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -33,6 +33,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createFloor, (), (override));
         MOCK_METHOD(void, createCeil, (), (override));
         MOCK_METHOD(void, createSqrt, (), (override));
+        MOCK_METHOD(void, createSin, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 2c3c62c6a429000dd4eedbee4f307afb9b769d34 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 6 Nov 2024 17:11:26 +0100
Subject: [PATCH 012/259] LLVMCodeBuilder: Implement cos operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 21 +++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 47 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 72 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index ad9769af..e98b8095 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -44,6 +44,7 @@ class ICodeBuilder
         virtual void createCeil() = 0;
         virtual void createSqrt() = 0;
         virtual void createSin() = 0;
+        virtual void createCos() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index ec448824..5e7bc88c 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -268,6 +268,22 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Cos: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // round(cos(x * pi / 180.0) * 1e10) / 1e10
+                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
+                llvm::Constant *factor = llvm::ConstantFP::get(m_ctx, llvm::APFloat(1e10));
+                llvm::Function *cosFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::cos, m_builder.getDoubleTy());
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                llvm::Value *cosResult = m_builder.CreateCall(cosFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // cos(x * pi / 180)
+                llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(cosResult, factor));                    // round(cos(x * 180) * 1e10)
+                step.functionReturnReg->value = m_builder.CreateFDiv(rounded, factor);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -671,6 +687,11 @@ void LLVMCodeBuilder::createSin()
     createOp(Step::Type::Sin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createCos()
+{
+    createOp(Step::Type::Cos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 2001a321..33e79606 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -46,6 +46,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createCeil() override;
         void createSqrt() override;
         void createSin() override;
+        void createCos() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -96,6 +97,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Ceil,
                     Sqrt,
                     Sin,
+                    Cos,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 7816db8e..6a0c82c3 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1369,6 +1369,53 @@ TEST_F(LLVMCodeBuilderTest, Sin)
     addOpTest(nan, 0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Cos)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createCos();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createCos();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(60.0, 0.5);
+    addOpTest(90.0, 0.0);
+    addOpTest(600.0, -0.5);
+    addOpTest(89.9999999971352, 1e-10);
+    addOpTest(89.999999999, 0.0);
+    addOpTest(-60.0, 0.5);
+    addOpTest(-90.0, 0.0);
+    addOpTest(0.0, 1.0);
+    addOpTest(-0.0, 1.0);
+    addOpTest(inf, -nan); // negative NaN shouldn't be a problem
+    addOpTest(-inf, -nan);
+    addOpTest(nan, 1.0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index a3202d3c..624f25e9 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -34,6 +34,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createCeil, (), (override));
         MOCK_METHOD(void, createSqrt, (), (override));
         MOCK_METHOD(void, createSin, (), (override));
+        MOCK_METHOD(void, createCos, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From f0c6eb5876cfa07b2c16a01aa159160e1a630572 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 6 Nov 2024 18:41:25 +0100
Subject: [PATCH 013/259] LLVMCodeBuilder: Implement tan operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 37 +++++++++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 51 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 92 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index e98b8095..a7dc10fa 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -45,6 +45,7 @@ class ICodeBuilder
         virtual void createSqrt() = 0;
         virtual void createSin() = 0;
         virtual void createCos() = 0;
+        virtual void createTan() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 5e7bc88c..bdeec57f 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -284,6 +284,38 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Tan: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // ((mod = rem(x, 360.0)) == -270.0 || mod == 90.0) ? inf : ((mod == -90.0 || mod == 270.0) ? -inf : round(tan(x * pi / 180.0) * 1e10) / 1e10 + 0.0)
+                // +0.0 to avoid -0.0
+                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
+                llvm::Constant *full = llvm::ConstantFP::get(m_ctx, llvm::APFloat(360.0));
+                llvm::Constant *posInf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false);
+                llvm::Constant *negInf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), true);
+                llvm::Constant *undefined1 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(-270.0));
+                llvm::Constant *undefined2 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(90.0));
+                llvm::Constant *undefined3 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(-90.0));
+                llvm::Constant *undefined4 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(270.0));
+                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
+                llvm::Constant *factor = llvm::ConstantFP::get(m_ctx, llvm::APFloat(1e10));
+                llvm::Function *tanFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::tan, m_builder.getDoubleTy());
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                llvm::Value *mod = m_builder.CreateFRem(num, full);
+                llvm::Value *isUndefined1 = m_builder.CreateFCmpOEQ(mod, undefined1);                                               // rem(x, 360.0) == -270.0
+                llvm::Value *isUndefined2 = m_builder.CreateFCmpOEQ(mod, undefined2);                                               // rem(x, 360.0) == 90.0
+                llvm::Value *isUndefined3 = m_builder.CreateFCmpOEQ(mod, undefined3);                                               // rem(x, 360.0) == -90.0
+                llvm::Value *isUndefined4 = m_builder.CreateFCmpOEQ(mod, undefined4);                                               // rem(x, 360.0) == 270.0
+                llvm::Value *tanResult = m_builder.CreateCall(tanFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // tan(x * pi / 180)
+                llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(tanResult, factor));                    // round(tan(x * 180) * 1e10)
+                llvm::Value *result = m_builder.CreateFAdd(m_builder.CreateFDiv(rounded, factor), zero);                            // round(tan(x * pi / 180.0) * 1e10) / 1e10 + 0.0
+                llvm::Value *inner = m_builder.CreateSelect(m_builder.CreateOr(isUndefined3, isUndefined4), negInf, result);
+                step.functionReturnReg->value = m_builder.CreateSelect(m_builder.CreateOr(isUndefined1, isUndefined2), posInf, inner);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -692,6 +724,11 @@ void LLVMCodeBuilder::createCos()
     createOp(Step::Type::Cos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createTan()
+{
+    createOp(Step::Type::Tan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 33e79606..6b3cf82e 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -47,6 +47,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createSqrt() override;
         void createSin() override;
         void createCos() override;
+        void createTan() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -98,6 +99,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Sqrt,
                     Sin,
                     Cos,
+                    Tan,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 6a0c82c3..9895640f 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1416,6 +1416,57 @@ TEST_F(LLVMCodeBuilderTest, Cos)
     addOpTest(nan, 1.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Tan)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createTan();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createTan();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(45.0, 1.0);
+    addOpTest(90.0, inf);
+    addOpTest(270.0, -inf);
+    addOpTest(450.0, inf);
+    addOpTest(-90.0, -inf);
+    addOpTest(-270.0, inf);
+    addOpTest(-450.0, -inf);
+    addOpTest(180.0, 0.0);
+    addOpTest(-180.0, 0.0);
+    addOpTest(2.87e-9, 1e-10);
+    addOpTest(2.8647e-9, 0.0);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, 0.0);
+    addOpTest(inf, -nan); // negative NaN shouldn't be a problem
+    addOpTest(-inf, -nan);
+    addOpTest(nan, 0.0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 624f25e9..ca33814a 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -35,6 +35,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createSqrt, (), (override));
         MOCK_METHOD(void, createSin, (), (override));
         MOCK_METHOD(void, createCos, (), (override));
+        MOCK_METHOD(void, createTan, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From e80226dc316206a080131ecef50969347ec48e1e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 14:12:21 +0100
Subject: [PATCH 014/259] LLVMCodeBuilder: Implement asin operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 19 +++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 46 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 69 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index a7dc10fa..735fed2f 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -46,6 +46,7 @@ class ICodeBuilder
         virtual void createSin() = 0;
         virtual void createCos() = 0;
         virtual void createTan() = 0;
+        virtual void createAsin() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index bdeec57f..8a3df394 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -316,6 +316,20 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Asin: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // asin(x) * 180.0 / pi + 0.0
+                // +0.0 to avoid -0.0
+                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
+                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
+                llvm::Function *asinFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::asin, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(asinFunc, num), piDeg), pi), zero);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -729,6 +743,11 @@ void LLVMCodeBuilder::createTan()
     createOp(Step::Type::Tan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createAsin()
+{
+    createOp(Step::Type::Asin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 6b3cf82e..01d5785e 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -48,6 +48,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createSin() override;
         void createCos() override;
         void createTan() override;
+        void createAsin() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -100,6 +101,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Sin,
                     Cos,
                     Tan,
+                    Asin,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 9895640f..18f83954 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1467,6 +1467,52 @@ TEST_F(LLVMCodeBuilderTest, Tan)
     addOpTest(nan, 0.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Asin)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createAsin();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createAsin();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(1.0, 90.0);
+    addOpTest(0.5, 30.0);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, 0.0);
+    addOpTest(-0.5, -30.0);
+    addOpTest(-1.0, -90.0);
+    addOpTest(1.1, nan);
+    addOpTest(-1.2, nan);
+    addOpTest(inf, nan);
+    addOpTest(-inf, nan);
+    addOpTest(nan, 0.0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index ca33814a..fe1eb403 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -36,6 +36,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createSin, (), (override));
         MOCK_METHOD(void, createCos, (), (override));
         MOCK_METHOD(void, createTan, (), (override));
+        MOCK_METHOD(void, createAsin, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 30017662f19a6f1750068306f958b7c324b705ce Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 14:29:38 +0100
Subject: [PATCH 015/259] LLVMCodeBuilder: Implement acos operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 17 ++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 46 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 67 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 735fed2f..da062b6c 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -47,6 +47,7 @@ class ICodeBuilder
         virtual void createCos() = 0;
         virtual void createTan() = 0;
         virtual void createAsin() = 0;
+        virtual void createAcos() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 8a3df394..821e5d71 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -330,6 +330,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Acos: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // acos(x) * 180.0 / pi
+                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
+                llvm::Function *acosFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::acos, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(acosFunc, num), piDeg), pi);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -748,6 +760,11 @@ void LLVMCodeBuilder::createAsin()
     createOp(Step::Type::Asin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createAcos()
+{
+    createOp(Step::Type::Acos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 01d5785e..ec66a0be 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -49,6 +49,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createCos() override;
         void createTan() override;
         void createAsin() override;
+        void createAcos() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -102,6 +103,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Cos,
                     Tan,
                     Asin,
+                    Acos,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 18f83954..9e1b2ccb 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1513,6 +1513,52 @@ TEST_F(LLVMCodeBuilderTest, Asin)
     addOpTest(nan, 0.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Acos)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createAcos();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createAcos();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(1.0, 0.0);
+    addOpTest(0.5, 60.0);
+    addOpTest(0.0, 90.0);
+    addOpTest(-0.0, 90.0);
+    addOpTest(-0.5, 120.0);
+    addOpTest(-1.0, 180.0);
+    addOpTest(1.1, nan);
+    addOpTest(-1.2, nan);
+    addOpTest(inf, nan);
+    addOpTest(-inf, nan);
+    addOpTest(nan, 90.0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index fe1eb403..67bbe38d 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -37,6 +37,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createCos, (), (override));
         MOCK_METHOD(void, createTan, (), (override));
         MOCK_METHOD(void, createAsin, (), (override));
+        MOCK_METHOD(void, createAcos, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 923ec2328e26c8bd1de163b50330f30a66c9c0af Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 16:38:58 +0100
Subject: [PATCH 016/259] LLVMCodeBuilder: Implement atan operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 17 +++++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 42 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 63 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index da062b6c..db9adc31 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -48,6 +48,7 @@ class ICodeBuilder
         virtual void createTan() = 0;
         virtual void createAsin() = 0;
         virtual void createAcos() = 0;
+        virtual void createAtan() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 821e5d71..473c3a4b 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -342,6 +342,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Atan: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // atan(x) * 180.0 / pi
+                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
+                llvm::Function *atanFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::atan, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(atanFunc, num), piDeg), pi);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -765,6 +777,11 @@ void LLVMCodeBuilder::createAcos()
     createOp(Step::Type::Acos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createAtan()
+{
+    createOp(Step::Type::Atan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index ec66a0be..7b180406 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -50,6 +50,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createTan() override;
         void createAsin() override;
         void createAcos() override;
+        void createAtan() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -104,6 +105,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Tan,
                     Asin,
                     Acos,
+                    Atan,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 9e1b2ccb..02deb187 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1559,6 +1559,48 @@ TEST_F(LLVMCodeBuilderTest, Acos)
     addOpTest(nan, 90.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Atan)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createAtan();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createAtan();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(1.0, 45.0);
+    addOpTest(0.0, 0.0);
+    addOpTest(-0.0, -0.0);
+    addOpTest(-1.0, -45.0);
+    addOpTest(inf, 90.0);
+    addOpTest(-inf, -90.0);
+    addOpTest(nan, 0.0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 67bbe38d..e405f2b0 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -38,6 +38,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createTan, (), (override));
         MOCK_METHOD(void, createAsin, (), (override));
         MOCK_METHOD(void, createAcos, (), (override));
+        MOCK_METHOD(void, createAtan, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From e80c54f3e18e60d1315184c92578d553e8e5c2ab Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 17:09:09 +0100
Subject: [PATCH 017/259] LLVMCodeBuilder: Implement ln operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 15 +++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 45 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 64 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index db9adc31..ce96d642 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -49,6 +49,7 @@ class ICodeBuilder
         virtual void createAsin() = 0;
         virtual void createAcos() = 0;
         virtual void createAtan() = 0;
+        virtual void createLn() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 473c3a4b..8977e83e 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -354,6 +354,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Ln: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // log(x)
+                llvm::Function *logFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::log, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateCall(logFunc, num);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -782,6 +792,11 @@ void LLVMCodeBuilder::createAtan()
     createOp(Step::Type::Atan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createLn()
+{
+    createOp(Step::Type::Ln, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 7b180406..f98a313f 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -51,6 +51,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createAsin() override;
         void createAcos() override;
         void createAtan() override;
+        void createLn() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -106,6 +107,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Asin,
                     Acos,
                     Atan,
+                    Ln,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 02deb187..0de1ae1e 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1601,6 +1601,51 @@ TEST_F(LLVMCodeBuilderTest, Atan)
     addOpTest(nan, 0.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Ln)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createLn();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createLn();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(std::exp(1.0), 1.0);
+    addOpTest(std::exp(2.0), 2.0);
+    addOpTest(std::exp(0.3), 0.3);
+    addOpTest(1.0, 0.0);
+    addOpTest(0.0, -inf);
+    addOpTest(-0.0, -inf);
+    addOpTest(-0.7, -nan); // negative NaN shouldn't be a problem
+    addOpTest(inf, inf);
+    addOpTest(-inf, -nan);
+    addOpTest(nan, -inf);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index e405f2b0..7b5a10f2 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -39,6 +39,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createAsin, (), (override));
         MOCK_METHOD(void, createAcos, (), (override));
         MOCK_METHOD(void, createAtan, (), (override));
+        MOCK_METHOD(void, createLn, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 2096b40ca6819307bd415edb35da6e356dafb5e4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 18:20:19 +0100
Subject: [PATCH 018/259] LLVMCodeBuilder: Implement log10 operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 15 +++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 44 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 63 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index ce96d642..205885bf 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -50,6 +50,7 @@ class ICodeBuilder
         virtual void createAcos() = 0;
         virtual void createAtan() = 0;
         virtual void createLn() = 0;
+        virtual void createLog10() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 8977e83e..d92ed612 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -364,6 +364,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Log10: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // log10(x)
+                llvm::Function *log10Func = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::log10, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateCall(log10Func, num);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -797,6 +807,11 @@ void LLVMCodeBuilder::createLn()
     createOp(Step::Type::Ln, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createLog10()
+{
+    createOp(Step::Type::Log10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index f98a313f..85c0fa39 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -52,6 +52,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createAcos() override;
         void createAtan() override;
         void createLn() override;
+        void createLog10() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -108,6 +109,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Acos,
                     Atan,
                     Ln,
+                    Log10,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 0de1ae1e..c872a154 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1646,6 +1646,50 @@ TEST_F(LLVMCodeBuilderTest, Ln)
     addOpTest(nan, -inf);
 }
 
+TEST_F(LLVMCodeBuilderTest, Log10)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createLog10();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createLog10();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(10.0, 1.0);
+    addOpTest(1000.0, 3.0);
+    addOpTest(0.01, -2.0);
+    addOpTest(0.0, -inf);
+    addOpTest(-0.0, -inf);
+    addOpTest(-0.7, nan);
+    addOpTest(inf, inf);
+    addOpTest(-inf, nan);
+    addOpTest(nan, -inf);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 7b5a10f2..cfaa6195 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -40,6 +40,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createAcos, (), (override));
         MOCK_METHOD(void, createAtan, (), (override));
         MOCK_METHOD(void, createLn, (), (override));
+        MOCK_METHOD(void, createLog10, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 1994f20a0e6b450982bcd6c85e5618e7dac37bc7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 18:48:57 +0100
Subject: [PATCH 019/259] LLVMCodeBuilder: Implement exp operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 15 +++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 43 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 62 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 205885bf..119cff7a 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -51,6 +51,7 @@ class ICodeBuilder
         virtual void createAtan() = 0;
         virtual void createLn() = 0;
         virtual void createLog10() = 0;
+        virtual void createExp() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index d92ed612..afc2198f 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -374,6 +374,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Exp: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // exp(x)
+                llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::exp, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateCall(expFunc, num);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -812,6 +822,11 @@ void LLVMCodeBuilder::createLog10()
     createOp(Step::Type::Log10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createExp()
+{
+    createOp(Step::Type::Exp, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 85c0fa39..289bea61 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -53,6 +53,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createAtan() override;
         void createLn() override;
         void createLog10() override;
+        void createExp() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -110,6 +111,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Atan,
                     Ln,
                     Log10,
+                    Exp,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index c872a154..b4b47ae6 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1690,6 +1690,49 @@ TEST_F(LLVMCodeBuilderTest, Log10)
     addOpTest(nan, -inf);
 }
 
+TEST_F(LLVMCodeBuilderTest, Exp)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createExp();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createExp();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(1.0, std::exp(1.0));
+    addOpTest(0.5, std::exp(0.5));
+    addOpTest(0.0, 1.0);
+    addOpTest(-0.0, 1.0);
+    addOpTest(-0.7, std::exp(-0.7));
+    addOpTest(inf, inf);
+    addOpTest(-inf, 0.0);
+    addOpTest(nan, 1.0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index cfaa6195..3bdcd51b 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -41,6 +41,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createAtan, (), (override));
         MOCK_METHOD(void, createLn, (), (override));
         MOCK_METHOD(void, createLog10, (), (override));
+        MOCK_METHOD(void, createExp, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 60b53c488e642088409460c6fa37963eb9799f8d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 19:00:16 +0100
Subject: [PATCH 020/259] LLVMCodeBuilder: Implement exp10 operator

---
 src/dev/engine/internal/icodebuilder.h      |  1 +
 src/dev/engine/internal/llvmcodebuilder.cpp | 15 +++++++
 src/dev/engine/internal/llvmcodebuilder.h   |  2 +
 test/dev/llvm/llvmcodebuilder_test.cpp      | 44 +++++++++++++++++++++
 test/mocks/codebuildermock.h                |  1 +
 5 files changed, 63 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 119cff7a..824471bf 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -52,6 +52,7 @@ class ICodeBuilder
         virtual void createLn() = 0;
         virtual void createLog10() = 0;
         virtual void createExp() = 0;
+        virtual void createExp10() = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index afc2198f..6d1c5872 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -384,6 +384,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::Exp10: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                // exp10(x)
+                llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::exp10, m_builder.getDoubleTy());
+                llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
+                step.functionReturnReg->value = m_builder.CreateCall(expFunc, num);
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -827,6 +837,11 @@ void LLVMCodeBuilder::createExp()
     createOp(Step::Type::Exp, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createExp10()
+{
+    createOp(Step::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 289bea61..9401e048 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -54,6 +54,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createLn() override;
         void createLog10() override;
         void createExp() override;
+        void createExp10() override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -112,6 +113,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Ln,
                     Log10,
                     Exp,
+                    Exp10,
                     Yield,
                     BeginIf,
                     BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index b4b47ae6..7c357278 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1733,6 +1733,50 @@ TEST_F(LLVMCodeBuilderTest, Exp)
     addOpTest(nan, 1.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Exp10)
+{
+    std::string expected;
+
+    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
+        createBuilder(true);
+
+        m_builder->addConstValue(v1);
+        m_builder->createExp10();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        m_builder->addConstValue(v1);
+        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+        m_builder->createExp10();
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+
+        std::stringstream stream;
+        stream << expectedResult;
+        std::string str = stream.str() + '\n';
+        std::string expected = str + str;
+
+        auto code = m_builder->finalize();
+        auto ctx = code->createExecutionContext(&m_target);
+
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        const std::string quotes1 = v1.isString() ? "\"" : "";
+        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
+    };
+
+    static const double inf = std::numeric_limits<double>::infinity();
+    static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    addOpTest(1.0, 10.0);
+    addOpTest(3.0, 1000.0);
+    addOpTest(0.0, 1.0);
+    addOpTest(-0.0, 1.0);
+    addOpTest(-1.0, 0.1);
+    addOpTest(-5.0, 0.00001);
+    addOpTest(inf, inf);
+    addOpTest(-inf, 0.0);
+    addOpTest(nan, 1.0);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 3bdcd51b..3de5cf4c 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -42,6 +42,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createLn, (), (override));
         MOCK_METHOD(void, createLog10, (), (override));
         MOCK_METHOD(void, createExp, (), (override));
+        MOCK_METHOD(void, createExp10, (), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From edb28bfbec58929cf83491d152bcfffdd75aa5fa Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 7 Nov 2024 19:10:48 +0100
Subject: [PATCH 021/259] Compiler: Add methods for math instructions

---
 include/scratchcpp/dev/compiler.h   |  17 +++
 src/dev/engine/compiler.cpp         |  96 +++++++++++++
 test/dev/compiler/compiler_test.cpp | 208 ++++++++++++++++++++++++++++
 3 files changed, 321 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 3b579023..d3700a88 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -61,6 +61,23 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void createOr();
         void createNot();
 
+        void createMod();
+        void createRound();
+        void createAbs();
+        void createFloor();
+        void createCeil();
+        void createSqrt();
+        void createSin();
+        void createCos();
+        void createTan();
+        void createAsin();
+        void createAcos();
+        void createAtan();
+        void createLn();
+        void createLog10();
+        void createExp();
+        void createExp10();
+
         void moveToIf(std::shared_ptr<Block> substack);
         void moveToIfElse(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2);
         void moveToRepeatLoop(std::shared_ptr<Block> substack);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 7ff6737e..1d036ba9 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -160,6 +160,102 @@ void Compiler::createNot()
     impl->builder->createNot();
 }
 
+/*! Creates a remainder operation using the last 2 values. */
+void Compiler::createMod()
+{
+    impl->builder->createMod();
+}
+
+/*! Creates a round operation using the last value. */
+void Compiler::createRound()
+{
+    impl->builder->createRound();
+}
+
+/*! Creates an abs operation using the last value. */
+void Compiler::createAbs()
+{
+    impl->builder->createAbs();
+}
+
+/*! Creates a floor operation using the last value. */
+void Compiler::createFloor()
+{
+    impl->builder->createFloor();
+}
+
+/*! Creates a ceiling operation using the last value. */
+void Compiler::createCeil()
+{
+    impl->builder->createCeil();
+}
+
+/*! Creates a square root operation using the last value. */
+void Compiler::createSqrt()
+{
+    impl->builder->createSqrt();
+}
+
+/*! Creates a sin operation using the last value. */
+void Compiler::createSin()
+{
+    impl->builder->createSin();
+}
+
+/*! Creates a cos operation using the last value. */
+void Compiler::createCos()
+{
+    impl->builder->createCos();
+}
+
+/*! Creates a tan operation using the last value. */
+void Compiler::createTan()
+{
+    impl->builder->createTan();
+}
+
+/*! Creates an asin operation using the last value. */
+void Compiler::createAsin()
+{
+    impl->builder->createAsin();
+}
+
+/*! Creates an acos operation using the last value. */
+void Compiler::createAcos()
+{
+    impl->builder->createAcos();
+}
+
+/*! Creates an atan operation using the last value. */
+void Compiler::createAtan()
+{
+    impl->builder->createAtan();
+}
+
+/*! Creates an ln operation using the last value. */
+void Compiler::createLn()
+{
+    impl->builder->createLn();
+}
+
+/*! Creates a log10 operation using the last value. */
+void Compiler::createLog10()
+{
+    impl->builder->createLog10();
+}
+
+/*! Creates an e^x operation using the last value. */
+void Compiler::createExp()
+{
+    impl->builder->createExp();
+}
+
+/*! Creates a 10^x operation using the last value. */
+void Compiler::createExp10()
+{
+    impl->builder->createExp10();
+}
+
 /*! Jumps to the given if substack. */
 void Compiler::moveToIf(std::shared_ptr<Block> substack)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 0ebe69c5..fc59fb86 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -349,6 +349,214 @@ TEST_F(CompilerTest, CreateNot)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CreateMod)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createMod);
+        compiler->createMod();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateRound)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createRound);
+        compiler->createRound();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateAbs)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createAbs);
+        compiler->createAbs();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateFloor)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createFloor);
+        compiler->createFloor();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateCeil)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createCeil);
+        compiler->createCeil();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateSqrt)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createSqrt);
+        compiler->createSqrt();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateSin)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createSin);
+        compiler->createSin();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateCos)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createCos);
+        compiler->createCos();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateTan)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createTan);
+        compiler->createTan();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateAsin)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createAsin);
+        compiler->createAsin();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateAcos)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createAcos);
+        compiler->createAcos();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateAtan)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createAtan);
+        compiler->createAtan();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateLn)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createLn);
+        compiler->createLn();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateLog10)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createLog10);
+        compiler->createLog10();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateExp)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createExp);
+        compiler->createExp();
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateExp10)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, createExp10);
+        compiler->createExp10();
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, MoveToIf)
 {
     Compiler compiler(&m_engine, &m_target);

From be0c9518f843c2012814277ba46e22ec84a8b543 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Nov 2024 19:48:38 +0100
Subject: [PATCH 022/259] LLVMCodeBuilder: Fix a typo in
 resolve_value_assign_bool()

---
 src/dev/engine/internal/llvmcodebuilder.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 6d1c5872..28ed3a39 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -1488,7 +1488,7 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_double()
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_bool()
 {
-    return resolveFunction("value_assign_double", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt1Ty() }, false));
+    return resolveFunction("value_assign_bool", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt1Ty() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_cstring()

From d88481361d25276d2495939e4645b6e521b8a7fc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Nov 2024 22:04:17 +0100
Subject: [PATCH 023/259] Target: Add variableData() method

---
 include/scratchcpp/target.h          |  5 ++++-
 src/scratch/target.cpp               | 33 ++++++++++++++++++++++++++++
 src/scratch/target_p.h               |  2 ++
 test/scratch_classes/target_test.cpp |  9 ++++++++
 4 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h
index 50c2ec71..b9d89eb5 100644
--- a/include/scratchcpp/target.h
+++ b/include/scratchcpp/target.h
@@ -12,6 +12,7 @@ namespace libscratchcpp
 {
 
 class Variable;
+struct ValueData;
 class List;
 class Block;
 class Comment;
@@ -28,7 +29,7 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable
     public:
         Target();
         Target(const Target &) = delete;
-        virtual ~Target() { }
+        virtual ~Target();
 
         bool isTarget() const override final;
 
@@ -44,6 +45,8 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable
         int findVariable(const std::string &variableName) const;
         int findVariableById(const std::string &id) const;
 
+        ValueData **variableData();
+
         const std::vector<std::shared_ptr<List>> &lists() const;
         int addList(std::shared_ptr<List> list);
         std::shared_ptr<List> listAt(int index) const;
diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index 7d01cebe..c25b2f38 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -29,6 +29,13 @@ Target::Target() :
 {
 }
 
+/*! Destroys Target. */
+Target::~Target()
+{
+    if (impl->variableData)
+        free(impl->variableData);
+}
+
 /*! Returns true. */
 bool Target::isTarget() const
 {
@@ -66,6 +73,7 @@ int Target::addVariable(std::shared_ptr<Variable> variable)
         return it - impl->variables.begin();
 
     impl->variables.push_back(variable);
+    impl->variableDataDirty = true;
     variable->setTarget(this);
 
     return impl->variables.size() - 1;
@@ -102,6 +110,31 @@ int Target::findVariableById(const std::string &id) const
         return it - impl->variables.begin();
 }
 
+/*! Returns an array of raw variable data pointers (for optimized variable access). */
+ValueData **Target::variableData()
+{
+    if (impl->variableDataDirty) {
+        const size_t len = impl->variables.size();
+
+        if (len == 0) {
+            impl->variableDataDirty = false;
+            return nullptr;
+        }
+
+        if (impl->variableData)
+            impl->variableData = (ValueData **)realloc(impl->variableData, len * sizeof(ValueData *));
+        else
+            impl->variableData = (ValueData **)malloc(len * sizeof(ValueData *));
+
+        for (size_t i = 0; i < len; i++)
+            impl->variableData[i] = &impl->variables[i]->valuePtr()->data();
+
+        impl->variableDataDirty = false;
+    }
+
+    return impl->variableData;
+}
+
 /*! Returns the list of Scratch lists. */
 const std::vector<std::shared_ptr<List>> &Target::lists() const
 {
diff --git a/src/scratch/target_p.h b/src/scratch/target_p.h
index eaf6206e..fc18bfb8 100644
--- a/src/scratch/target_p.h
+++ b/src/scratch/target_p.h
@@ -28,6 +28,8 @@ struct TargetPrivate
 
         std::string name;
         std::vector<std::shared_ptr<Variable>> variables;
+        bool variableDataDirty = true;
+        ValueData **variableData = nullptr;
         std::vector<std::shared_ptr<List>> lists;
         std::vector<std::shared_ptr<Block>> blocks;
         std::vector<std::shared_ptr<Comment>> comments;
diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index e9d02379..f83f46fa 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -75,10 +75,19 @@ TEST(TargetTest, Variables)
     auto v3 = std::make_shared<Variable>("c", "var3");
 
     Target target;
+    ASSERT_EQ(target.variableData(), nullptr);
     ASSERT_EQ(target.addVariable(v1), 0);
+    ASSERT_TRUE(target.variableData());
+    ASSERT_EQ(target.variableData()[0], &v1->value().data());
     ASSERT_EQ(target.addVariable(v2), 1);
     ASSERT_EQ(target.addVariable(v3), 2);
+    ASSERT_EQ(target.variableData()[0], &v1->value().data());
+    ASSERT_EQ(target.variableData()[1], &v2->value().data());
+    ASSERT_EQ(target.variableData()[2], &v3->value().data());
     ASSERT_EQ(target.addVariable(v2), 1); // add existing variable
+    ASSERT_EQ(target.variableData()[0], &v1->value().data());
+    ASSERT_EQ(target.variableData()[1], &v2->value().data());
+    ASSERT_EQ(target.variableData()[2], &v3->value().data());
 
     ASSERT_EQ(v1->target(), &target);
     ASSERT_EQ(v2->target(), &target);

From 372265c379473ac5c65e342938f1835c31beb95a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Nov 2024 23:56:09 +0100
Subject: [PATCH 024/259] Add target to LLVMCodeBuilder

---
 src/dev/engine/compiler.cpp                    | 2 +-
 src/dev/engine/internal/codebuilderfactory.cpp | 4 ++--
 src/dev/engine/internal/codebuilderfactory.h   | 2 +-
 src/dev/engine/internal/icodebuilderfactory.h  | 3 ++-
 src/dev/engine/internal/llvmcodebuilder.cpp    | 3 ++-
 src/dev/engine/internal/llvmcodebuilder.h      | 4 +++-
 test/dev/compiler/compiler_test.cpp            | 2 +-
 test/dev/llvm/llvmcodebuilder_test.cpp         | 3 ++-
 test/mocks/codebuilderfactorymock.h            | 2 +-
 9 files changed, 15 insertions(+), 10 deletions(-)

diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 1d036ba9..6181c510 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -38,7 +38,7 @@ std::shared_ptr<libscratchcpp::Block> Compiler::block() const
 /*! Compiles the script starting with the given block. */
 std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBlock)
 {
-    impl->builder = impl->builderFactory->create(startBlock->id(), false);
+    impl->builder = impl->builderFactory->create(impl->target, startBlock->id(), false);
     impl->substackTree.clear();
     impl->substackHit = false;
     impl->warp = false;
diff --git a/src/dev/engine/internal/codebuilderfactory.cpp b/src/dev/engine/internal/codebuilderfactory.cpp
index 5f955bdc..dfa048ee 100644
--- a/src/dev/engine/internal/codebuilderfactory.cpp
+++ b/src/dev/engine/internal/codebuilderfactory.cpp
@@ -12,7 +12,7 @@ std::shared_ptr<CodeBuilderFactory> CodeBuilderFactory::instance()
     return m_instance;
 }
 
-std::shared_ptr<ICodeBuilder> CodeBuilderFactory::create(const std::string &id, bool warp) const
+std::shared_ptr<ICodeBuilder> CodeBuilderFactory::create(Target *target, const std::string &id, bool warp) const
 {
-    return std::make_shared<LLVMCodeBuilder>(id, warp);
+    return std::make_shared<LLVMCodeBuilder>(target, id, warp);
 }
diff --git a/src/dev/engine/internal/codebuilderfactory.h b/src/dev/engine/internal/codebuilderfactory.h
index 8de574ab..9a498717 100644
--- a/src/dev/engine/internal/codebuilderfactory.h
+++ b/src/dev/engine/internal/codebuilderfactory.h
@@ -11,7 +11,7 @@ class CodeBuilderFactory : public ICodeBuilderFactory
 {
     public:
         static std::shared_ptr<CodeBuilderFactory> instance();
-        std::shared_ptr<ICodeBuilder> create(const std::string &id, bool warp) const override;
+        std::shared_ptr<ICodeBuilder> create(Target *target, const std::string &id, bool warp) const override;
 
     private:
         static std::shared_ptr<CodeBuilderFactory> m_instance;
diff --git a/src/dev/engine/internal/icodebuilderfactory.h b/src/dev/engine/internal/icodebuilderfactory.h
index 4438e5f8..07265463 100644
--- a/src/dev/engine/internal/icodebuilderfactory.h
+++ b/src/dev/engine/internal/icodebuilderfactory.h
@@ -8,13 +8,14 @@ namespace libscratchcpp
 {
 
 class ICodeBuilder;
+class Target;
 
 class ICodeBuilderFactory
 {
     public:
         virtual ~ICodeBuilderFactory() { }
 
-        virtual std::shared_ptr<ICodeBuilder> create(const std::string &id, bool warp) const = 0;
+        virtual std::shared_ptr<ICodeBuilder> create(Target *target, const std::string &id, bool warp) const = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 28ed3a39..c8495c0c 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -13,7 +13,8 @@ using namespace libscratchcpp;
 static std::unordered_map<ValueType, Compiler::StaticType>
     TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } };
 
-LLVMCodeBuilder::LLVMCodeBuilder(const std::string &id, bool warp) :
+LLVMCodeBuilder::LLVMCodeBuilder(Target *target, const std::string &id, bool warp) :
+    m_target(target),
     m_id(id),
     m_module(std::make_unique<llvm::Module>(id, m_ctx)),
     m_builder(m_ctx),
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index 9401e048..a8353c3b 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -17,7 +17,7 @@ class Target;
 class LLVMCodeBuilder : public ICodeBuilder
 {
     public:
-        LLVMCodeBuilder(const std::string &id, bool warp);
+        LLVMCodeBuilder(Target *target, const std::string &id, bool warp);
 
         std::shared_ptr<ExecutableCode> finalize() override;
 
@@ -214,6 +214,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_value_lower();
         llvm::FunctionCallee resolve_strcasecmp();
 
+        Target *m_target = nullptr;
+
         std::string m_id;
         llvm::LLVMContext m_ctx;
         std::unique_ptr<llvm::Module> m_module;
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index fc59fb86..78098454 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -41,7 +41,7 @@ class CompilerTest : public testing::Test
         {
             ASSERT_EQ(compiler.block(), nullptr);
             // TODO: Test warp
-            EXPECT_CALL(m_builderFactory, create(block->id(), false)).WillOnce(Return(m_builder));
+            EXPECT_CALL(m_builderFactory, create(compiler.target(), block->id(), false)).WillOnce(Return(m_builder));
             EXPECT_CALL(*m_builder, finalize()).WillOnce(Return(m_code));
             ASSERT_EQ(compiler.compile(block), m_code);
             ASSERT_EQ(compiler.block(), nullptr);
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 7c357278..b5fbab80 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -19,7 +19,8 @@ class LLVMCodeBuilderTest : public testing::Test
             test_function(nullptr, nullptr); // force dependency
         }
 
-        void createBuilder(bool warp) { m_builder = std::make_unique<LLVMCodeBuilder>("test", warp); }
+        void createBuilder(Target *target, bool warp) { m_builder = std::make_unique<LLVMCodeBuilder>(target, "test", warp); }
+        void createBuilder(bool warp) { createBuilder(nullptr, warp); }
 
         void callConstFuncForType(ValueType type)
         {
diff --git a/test/mocks/codebuilderfactorymock.h b/test/mocks/codebuilderfactorymock.h
index 7bf04a5b..c561cc84 100644
--- a/test/mocks/codebuilderfactorymock.h
+++ b/test/mocks/codebuilderfactorymock.h
@@ -8,5 +8,5 @@ using namespace libscratchcpp;
 class CodeBuilderFactoryMock : public ICodeBuilderFactory
 {
     public:
-        MOCK_METHOD(std::shared_ptr<ICodeBuilder>, create, (const std::string &, bool), (const, override));
+        MOCK_METHOD(std::shared_ptr<ICodeBuilder>, create, (Target *, const std::string &, bool), (const, override));
 };

From 85dd5b8dfec4efa77832a63747e7fd6f2292285b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Nov 2024 23:59:14 +0100
Subject: [PATCH 025/259] LLVMCodeBuilder: Handle unknown type in cast methods

---
 src/dev/engine/internal/llvmcodebuilder.cpp | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index c8495c0c..051e8157 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -1044,8 +1044,9 @@ llvm::Value *LLVMCodeBuilder::castValue(std::shared_ptr<Register> reg, Compiler:
                     return m_builder.CreateSIToFP(boolValue, m_builder.getDoubleTy());
                 }
 
-                case Compiler::StaticType::String: {
-                    // Convert string to double
+                case Compiler::StaticType::String:
+                case Compiler::StaticType::Unknown: {
+                    // Convert to double
                     return m_builder.CreateCall(resolve_value_toDouble(), reg->value);
                 }
 
@@ -1070,7 +1071,8 @@ llvm::Value *LLVMCodeBuilder::castValue(std::shared_ptr<Register> reg, Compiler:
                 }
 
                 case Compiler::StaticType::String:
-                    // Convert string to bool
+                case Compiler::StaticType::Unknown:
+                    // Convert to bool
                     return m_builder.CreateCall(resolve_value_toBool(), reg->value);
 
                 default:
@@ -1081,7 +1083,8 @@ llvm::Value *LLVMCodeBuilder::castValue(std::shared_ptr<Register> reg, Compiler:
         case Compiler::StaticType::String:
             switch (reg->type) {
                 case Compiler::StaticType::Number:
-                case Compiler::StaticType::Bool: {
+                case Compiler::StaticType::Bool:
+                case Compiler::StaticType::Unknown: {
                     // Cast to string
                     llvm::Value *ptr = m_builder.CreateCall(resolve_value_toCString(), reg->value);
                     m_heap.push_back(ptr); // deallocate later

From e2b353449fa3b199e079b31b5c915387669cedc0 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 00:02:16 +0100
Subject: [PATCH 026/259] Implement LLVM variables

---
 src/dev/engine/internal/icodebuilder.h        |   2 +
 src/dev/engine/internal/llvmcodebuilder.cpp   | 196 +++++++++++++++++-
 src/dev/engine/internal/llvmcodebuilder.h     |  26 ++-
 .../engine/internal/llvmexecutablecode.cpp    |   5 +-
 src/dev/engine/internal/llvmexecutablecode.h  |   2 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 121 +++++++++++
 test/dev/llvm/llvmexecutablecode_test.cpp     |   4 +-
 test/mocks/codebuildermock.h                  |   2 +
 test/scratch_classes/inputvalue_test.cpp      |   2 +-
 9 files changed, 344 insertions(+), 16 deletions(-)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 824471bf..8ca7fbea 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -54,6 +54,8 @@ class ICodeBuilder
         virtual void createExp() = 0;
         virtual void createExp10() = 0;
 
+        virtual void createVariableWrite(Variable *variable) = 0;
+
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
         virtual void endIf() = 0;
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp
index 051e8157..96a38b22 100644
--- a/src/dev/engine/internal/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvmcodebuilder.cpp
@@ -5,6 +5,10 @@
 #include <llvm/ExecutionEngine/Orc/LLJIT.h>
 #include <llvm/Passes/PassBuilder.h>
 
+#include <scratchcpp/stage.h>
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/variable.h>
+
 #include "llvmcodebuilder.h"
 #include "llvmexecutablecode.h"
 
@@ -28,6 +32,7 @@ LLVMCodeBuilder::LLVMCodeBuilder(Target *target, const std::string &id, bool war
     m_constValues.push_back({});
     m_regs.push_back({});
     initTypes();
+    createVariableMap();
 }
 
 std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
@@ -41,10 +46,12 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     }
 
     // Create function
-    // void *f(Target *)
+    // void *f(Target *, ValueData **)
     llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, pointerType, false);
+    llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType }, false);
     llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
+    llvm::Value *targetPtr = func->getArg(0);
+    llvm::Value *targetVariables = func->getArg(1);
 
     llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
     m_builder.SetInsertPoint(entry);
@@ -59,6 +66,20 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     std::vector<Loop> loops;
     m_heap.clear();
 
+    // Create variable pointers
+    for (auto &[var, varPtr] : m_variablePtrs) {
+        llvm::Value *ptr = getVariablePtr(targetVariables, var);
+
+        // Access variable directly (slow?)
+        // varPtr.ptr = ptr;
+
+        // All variables are currently copied to the stack and synced later (seems to be faster)
+        // NOTE: Strings are NOT copied, only the pointer and string size are copied
+        varPtr.ptr = m_builder.CreateAlloca(m_valueDataType);
+        varPtr.onStack = true;
+        createValueCopy(ptr, varPtr.ptr);
+    }
+
     // Execute recorded steps
     for (const Step &step : m_steps) {
         switch (step.type) {
@@ -67,9 +88,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 std::vector<llvm::Value *> args;
 
                 // Add target pointer arg
-                assert(func->arg_size() == 1);
                 types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0));
-                args.push_back(func->getArg(0));
+                args.push_back(targetPtr);
 
                 // Args
                 for (auto &arg : step.args) {
@@ -395,9 +415,27 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case Step::Type::WriteVariable: {
+                assert(step.args.size() == 1);
+                assert(m_variablePtrs.find(step.workVariable) != m_variablePtrs.cend());
+                const auto &arg = step.args[0];
+                VariablePtr &varPtr = m_variablePtrs[step.workVariable];
+                varPtr.changed = true;
+                createValueStore(arg.second, varPtr.ptr);
+                break;
+            }
+
+            case Step::Type::ReadVariable: {
+                assert(step.args.size() == 0);
+                const VariablePtr &varPtr = m_variablePtrs[step.workVariable];
+                step.functionReturnReg->value = varPtr.ptr;
+                break;
+            }
+
             case Step::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
+                    syncVariables(targetVariables);
                     m_builder.CreateStore(m_builder.getInt1(true), coro.didSuspend);
                     llvm::BasicBlock *resumeBranch = llvm::BasicBlock::Create(m_ctx, "", func);
                     llvm::Value *noneToken = llvm::ConstantTokenNone::get(m_ctx);
@@ -608,6 +646,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     }
 
     freeHeap();
+    syncVariables(targetVariables);
 
     // Add final suspend point
     if (!m_warp) {
@@ -707,6 +746,18 @@ void LLVMCodeBuilder::addConstValue(const Value &value)
 
 void LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
+    // TODO: Implement type prediction
+    Step step(Step::Type::ReadVariable);
+    step.workVariable = variable;
+    m_variablePtrs[variable] = VariablePtr();
+
+    auto ret = std::make_shared<Register>(Compiler::StaticType::Unknown);
+    ret->isRawValue = false;
+    step.functionReturnReg = ret;
+    m_regs[m_currentFunction].push_back(ret);
+    m_tmpRegs.push_back(ret);
+
+    m_steps.push_back(step);
 }
 
 void LLVMCodeBuilder::addListContents(List *list)
@@ -843,6 +894,13 @@ void LLVMCodeBuilder::createExp10()
     createOp(Step::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
+void LLVMCodeBuilder::createVariableWrite(Variable *variable)
+{
+    Step &step = createOp(Step::Type::WriteVariable, Compiler::StaticType::Void, Compiler::StaticType::Unknown, 1);
+    step.workVariable = variable;
+    m_variablePtrs[variable] = VariablePtr();
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     Step step(Step::Type::BeginIf);
@@ -926,6 +984,36 @@ void LLVMCodeBuilder::initTypes()
     m_valueDataType->setBody({ unionType, valueType, sizeType });
 }
 
+void LLVMCodeBuilder::createVariableMap()
+{
+    if (!m_target)
+        return;
+
+    // Map variable pointers to variable data array indices
+    const auto &variables = m_target->variables();
+    ValueData **variableData = m_target->variableData();
+    const size_t len = variables.size();
+    m_targetVariableMap.clear();
+    m_targetVariableMap.reserve(len);
+
+    size_t i, j;
+
+    for (i = 0; i < len; i++) {
+        Variable *var = variables[i].get();
+
+        // Find the data for this variable
+        for (j = 0; j < len; j++) {
+            if (variableData[j] == &var->valuePtr()->data())
+                break;
+        }
+
+        if (j < len)
+            m_targetVariableMap[var] = j;
+        else
+            assert(false);
+    }
+}
+
 LLVMCodeBuilder::Coroutine LLVMCodeBuilder::initCoroutine(llvm::Function *func)
 {
     // Set presplitcoroutine attribute
@@ -1224,7 +1312,32 @@ llvm::Value *LLVMCodeBuilder::removeNaN(llvm::Value *num)
     return m_builder.CreateSelect(isNaN(num), llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)), num);
 }
 
-void LLVMCodeBuilder::createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
+llvm::Value *LLVMCodeBuilder::getVariablePtr(llvm::Value *targetVariables, Variable *variable)
+{
+    if (!m_target->isStage() && variable->target() == m_target) {
+        // If this is a local sprite variable, use the variable array at runtime (for clones)
+        assert(m_targetVariableMap.find(variable) != m_targetVariableMap.cend());
+        const size_t index = m_targetVariableMap[variable];
+        llvm::Value *ptr = m_builder.CreateGEP(m_valueDataType->getPointerTo(), targetVariables, m_builder.getInt64(index));
+        return m_builder.CreateLoad(m_valueDataType->getPointerTo(), ptr);
+    }
+
+    // Otherwise create a raw pointer at compile time
+    llvm::Value *addr = m_builder.getInt64((uintptr_t)&variable->value().data());
+    return m_builder.CreateIntToPtr(addr, m_valueDataType->getPointerTo());
+}
+
+void LLVMCodeBuilder::syncVariables(llvm::Value *targetVariables)
+{
+    for (auto &[var, varPtr] : m_variablePtrs) {
+        if (varPtr.onStack && varPtr.changed)
+            createValueCopy(varPtr.ptr, getVariablePtr(targetVariables, var));
+
+        varPtr.changed = false;
+    }
+}
+
+LLVMCodeBuilder::Step &LLVMCodeBuilder::createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
 {
     Step step(type);
 
@@ -1236,13 +1349,71 @@ void LLVMCodeBuilder::createOp(Step::Type type, Compiler::StaticType retType, Co
 
     m_tmpRegs.erase(m_tmpRegs.end() - argCount, m_tmpRegs.end());
 
-    auto ret = std::make_shared<Register>(retType);
-    ret->isRawValue = true;
-    step.functionReturnReg = ret;
-    m_regs[m_currentFunction].push_back(ret);
-    m_tmpRegs.push_back(ret);
+    if (retType != Compiler::StaticType::Void) {
+        auto ret = std::make_shared<Register>(retType);
+        ret->isRawValue = true;
+        step.functionReturnReg = ret;
+        m_regs[m_currentFunction].push_back(ret);
+        m_tmpRegs.push_back(ret);
+    }
 
     m_steps.push_back(step);
+    return m_steps.back();
+}
+
+void LLVMCodeBuilder::createValueStore(std::shared_ptr<Register> reg, llvm::Value *targetPtr)
+{
+    // TODO: Implement type prediction
+    Compiler::StaticType type = reg->type;
+    llvm::Value *converted = nullptr;
+
+    // Optimize string constants that represent numbers
+    if (reg->isConstValue && reg->type == Compiler::StaticType::String && reg->constValue.isValidNumber())
+        type = Compiler::StaticType::Number;
+
+    switch (type) {
+        case Compiler::StaticType::Number:
+            converted = castValue(reg, type);
+            m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted });
+            /*{
+                llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                m_builder.CreateStore(converted, ptr);
+            }*/
+            break;
+
+        case Compiler::StaticType::Bool:
+            converted = castValue(reg, type);
+            m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted });
+            break;
+
+        case Compiler::StaticType::String:
+            converted = castValue(reg, type);
+            m_builder.CreateCall(resolve_value_assign_cstring(), { targetPtr, converted });
+            break;
+
+        case Compiler::StaticType::Unknown:
+            m_builder.CreateCall(resolve_value_assign_copy(), { targetPtr, reg->value });
+            break;
+
+        default:
+            assert(false);
+            break;
+    }
+}
+
+void LLVMCodeBuilder::createValueCopy(llvm::Value *source, llvm::Value *target)
+{
+    // NOTE: This doesn't copy strings, but only the pointers
+    copyStructField(source, target, 0, m_valueDataType, m_builder.getInt64Ty()); // value
+    copyStructField(source, target, 1, m_valueDataType, m_builder.getInt32Ty()); // type
+    copyStructField(source, target, 2, m_valueDataType, m_builder.getInt64Ty()); // string size
+}
+
+void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType)
+{
+    llvm::Value *sourceField = m_builder.CreateStructGEP(structType, source, index);
+    llvm::Value *targetField = m_builder.CreateStructGEP(structType, target, index);
+    m_builder.CreateStore(m_builder.CreateLoad(fieldType, sourceField), targetField);
 }
 
 llvm::Value *LLVMCodeBuilder::createValue(std::shared_ptr<Register> reg)
@@ -1505,6 +1676,11 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_special()
     return resolveFunction("value_assign_special", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt32Ty() }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_copy()
+{
+    return resolveFunction("value_assign_copy", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_valueDataType->getPointerTo() }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toDouble()
 {
     return resolveFunction("value_toDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), m_valueDataType->getPointerTo(), false));
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h
index a8353c3b..704d82da 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvmcodebuilder.h
@@ -56,6 +56,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createExp() override;
         void createExp10() override;
 
+        void createVariableWrite(Variable *variable) override;
+
         void beginIfStatement() override;
         void beginElseBranch() override;
         void endIf() override;
@@ -114,6 +116,8 @@ class LLVMCodeBuilder : public ICodeBuilder
                     Log10,
                     Exp,
                     Exp10,
+                    WriteVariable,
+                    ReadVariable,
                     Yield,
                     BeginIf,
                     BeginElse,
@@ -135,6 +139,7 @@ class LLVMCodeBuilder : public ICodeBuilder
                 std::vector<std::pair<Compiler::StaticType, std::shared_ptr<Register>>> args; // target type, register
                 Compiler::StaticType functionReturnType = Compiler::StaticType::Void;
                 std::shared_ptr<Register> functionReturnReg;
+                Variable *workVariable = nullptr; // for variable write
         };
 
         struct IfStatement
@@ -163,6 +168,14 @@ class LLVMCodeBuilder : public ICodeBuilder
                 llvm::Value *didSuspend = nullptr;
         };
 
+        struct VariablePtr
+        {
+                llvm::Value *ptr = nullptr;
+                Compiler::StaticType type = Compiler::StaticType::Unknown;
+                bool onStack = false;
+                bool changed = false;
+        };
+
         struct Procedure
         {
                 // TODO: Implement procedures
@@ -177,6 +190,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         };
 
         void initTypes();
+        void createVariableMap();
 
         Coroutine initCoroutine(llvm::Function *func);
         void verifyFunction(llvm::Function *func);
@@ -190,7 +204,14 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *isNaN(llvm::Value *num);
         llvm::Value *removeNaN(llvm::Value *num);
 
-        void createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
+        llvm::Value *getVariablePtr(llvm::Value *targetVariables, Variable *variable);
+        void syncVariables(llvm::Value *targetVariables);
+
+        Step &createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
+
+        void createValueStore(std::shared_ptr<Register> reg, llvm::Value *targetPtr);
+        void createValueCopy(llvm::Value *source, llvm::Value *target);
+        void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
         llvm::Value *createValue(std::shared_ptr<Register> reg);
         llvm::Value *createComparison(std::shared_ptr<Register> arg1, std::shared_ptr<Register> arg2, Comparison type);
 
@@ -202,6 +223,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_value_assign_bool();
         llvm::FunctionCallee resolve_value_assign_cstring();
         llvm::FunctionCallee resolve_value_assign_special();
+        llvm::FunctionCallee resolve_value_assign_copy();
         llvm::FunctionCallee resolve_value_toDouble();
         llvm::FunctionCallee resolve_value_toBool();
         llvm::FunctionCallee resolve_value_toCString();
@@ -215,6 +237,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
+        std::unordered_map<Variable *, size_t> m_targetVariableMap;
+        std::unordered_map<Variable *, VariablePtr> m_variablePtrs;
 
         std::string m_id;
         llvm::LLVMContext m_ctx;
diff --git a/src/dev/engine/internal/llvmexecutablecode.cpp b/src/dev/engine/internal/llvmexecutablecode.cpp
index 6c2059ba..3357be56 100644
--- a/src/dev/engine/internal/llvmexecutablecode.cpp
+++ b/src/dev/engine/internal/llvmexecutablecode.cpp
@@ -2,6 +2,8 @@
 
 #include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/value_functions.h>
+#include <scratchcpp/stage.h>
+#include <scratchcpp/iengine.h>
 #include <llvm/Support/Error.h>
 #include <iostream>
 
@@ -52,7 +54,8 @@ void LLVMExecutableCode::run(ExecutionContext *context)
 
         ctx->setFinished(done);
     } else {
-        void *handle = m_mainFunction(context->target());
+        Target *target = ctx->target();
+        void *handle = m_mainFunction(target, target->variableData());
 
         if (!handle)
             ctx->setFinished(true);
diff --git a/src/dev/engine/internal/llvmexecutablecode.h b/src/dev/engine/internal/llvmexecutablecode.h
index 4094fee8..e066cd9c 100644
--- a/src/dev/engine/internal/llvmexecutablecode.h
+++ b/src/dev/engine/internal/llvmexecutablecode.h
@@ -32,7 +32,7 @@ class LLVMExecutableCode : public ExecutableCode
     private:
         uint64_t lookupFunction(const std::string &name);
 
-        using MainFunctionType = void *(*)(Target *);
+        using MainFunctionType = void *(*)(Target *, ValueData **);
         using ResumeFunctionType = bool (*)(void *);
 
         static LLVMExecutionContext *getContext(ExecutionContext *context);
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index b5fbab80..3c284571 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1,8 +1,12 @@
 #include <scratchcpp/value.h>
 #include <scratchcpp/dev/executablecode.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/stage.h>
+#include <scratchcpp/variable.h>
 #include <dev/engine/internal/llvmcodebuilder.h>
 #include <gmock/gmock.h>
 #include <targetmock.h>
+#include <enginemock.h>
 
 #include "testfunctions.h"
 
@@ -1778,6 +1782,123 @@ TEST_F(LLVMCodeBuilderTest, Exp10)
     addOpTest(nan, 1.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, WriteVariable)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar1 = std::make_shared<Variable>("", "");
+    auto globalVar2 = std::make_shared<Variable>("", "");
+    auto globalVar3 = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar1);
+    stage.addVariable(globalVar2);
+    stage.addVariable(globalVar3);
+
+    auto localVar1 = std::make_shared<Variable>("", "");
+    auto localVar2 = std::make_shared<Variable>("", "");
+    auto localVar3 = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar1);
+    sprite.addVariable(localVar2);
+    sprite.addVariable(localVar3);
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(5);
+    m_builder->createVariableWrite(globalVar1.get());
+
+    m_builder->addConstValue(-23.5);
+    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
+    m_builder->createVariableWrite(globalVar2.get());
+
+    m_builder->addConstValue("test");
+    m_builder->createVariableWrite(globalVar3.get());
+
+    m_builder->addConstValue("abc");
+    m_builder->createVariableWrite(localVar1.get());
+
+    m_builder->addConstValue("hello world");
+    m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String });
+    m_builder->createVariableWrite(localVar1.get());
+
+    m_builder->addConstValue(false);
+    m_builder->createVariableWrite(localVar2.get());
+
+    m_builder->addConstValue(true);
+    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
+    m_builder->createVariableWrite(localVar3.get());
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+
+    ASSERT_EQ(globalVar1->value(), 5);
+    ASSERT_EQ(globalVar2->value(), -23.5);
+    ASSERT_EQ(globalVar3->value(), "test");
+    ASSERT_EQ(localVar1->value(), "hello world");
+    ASSERT_EQ(localVar2->value(), false);
+    ASSERT_EQ(localVar3->value(), true);
+}
+
+TEST_F(LLVMCodeBuilderTest, ReadVariable)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar1 = std::make_shared<Variable>("", "", 87);
+    auto globalVar2 = std::make_shared<Variable>("", "", 6.4);
+    auto globalVar3 = std::make_shared<Variable>("", "", "abc");
+    stage.addVariable(globalVar1);
+    stage.addVariable(globalVar2);
+    stage.addVariable(globalVar3);
+
+    auto localVar1 = std::make_shared<Variable>("", "", false);
+    auto localVar2 = std::make_shared<Variable>("", "", true);
+    auto localVar3 = std::make_shared<Variable>("", "", "test");
+    sprite.addVariable(localVar1);
+    sprite.addVariable(localVar2);
+    sprite.addVariable(localVar3);
+
+    createBuilder(&sprite, true);
+
+    m_builder->addVariableValue(globalVar1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(globalVar2.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(globalVar3.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar2.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar3.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    std::string expected;
+    expected += globalVar1->value().toString() + '\n';
+    expected += globalVar2->value().toString() + '\n';
+    expected += globalVar3->value().toString() + '\n';
+    expected += localVar1->value().toString() + '\n';
+    expected += localVar2->value().toString() + '\n';
+    expected += localVar3->value().toString() + '\n';
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp
index fea3f778..525f3ef3 100644
--- a/test/dev/llvm/llvmexecutablecode_test.cpp
+++ b/test/dev/llvm/llvmexecutablecode_test.cpp
@@ -29,9 +29,9 @@ class LLVMExecutableCodeTest : public testing::Test
 
         llvm::Function *beginMainFunction()
         {
-            // void *f(Target *)
+            // void *f(Target *, ValueData **, ValueData **)
             llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-            llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, pointerType, false);
+            llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType }, false);
             llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
 
             llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 3de5cf4c..0028f76b 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -44,6 +44,8 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createExp, (), (override));
         MOCK_METHOD(void, createExp10, (), (override));
 
+        MOCK_METHOD(void, createVariableWrite, (Variable *), (override));
+
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));
         MOCK_METHOD(void, endIf, (), (override));
diff --git a/test/scratch_classes/inputvalue_test.cpp b/test/scratch_classes/inputvalue_test.cpp
index d26dd9d5..3cf6cc3f 100644
--- a/test/scratch_classes/inputvalue_test.cpp
+++ b/test/scratch_classes/inputvalue_test.cpp
@@ -130,7 +130,7 @@ TEST(InputValueTest, Compile)
     Compiler compiler(&engine, &target);
 
     auto block = std::make_shared<Block>("", "");
-    EXPECT_CALL(builderFactory, create(_, _)).WillOnce(Return(builder));
+    EXPECT_CALL(builderFactory, create(_, _, _)).WillOnce(Return(builder));
     EXPECT_CALL(*builder, finalize);
     compiler.compile(block);
     CompilerPrivate::builderFactory = nullptr;

From f004d98b5abd580409c952c7a7b424f31f54c7b9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 11:15:03 +0100
Subject: [PATCH 027/259] Compiler: Add createVariableWrite() method

---
 include/scratchcpp/dev/compiler.h   |  2 ++
 src/dev/engine/compiler.cpp         |  6 ++++++
 test/dev/compiler/compiler_test.cpp | 14 ++++++++++++++
 3 files changed, 22 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index d3700a88..f5b2c7fb 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -78,6 +78,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void createExp();
         void createExp10();
 
+        void createVariableWrite(Variable *variable);
+
         void moveToIf(std::shared_ptr<Block> substack);
         void moveToIfElse(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2);
         void moveToRepeatLoop(std::shared_ptr<Block> substack);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 6181c510..1dde27ab 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -256,6 +256,12 @@ void Compiler::createExp10()
     impl->builder->createExp10();
 }
 
+/*! Creates a variable write operation using the last value. */
+void Compiler::createVariableWrite(Variable *variable)
+{
+    impl->builder->createVariableWrite(variable);
+}
+
 /*! Jumps to the given if substack. */
 void Compiler::moveToIf(std::shared_ptr<Block> substack)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 78098454..d8cf8207 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -557,6 +557,20 @@ TEST_F(CompilerTest, CreateExp10)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CreateVariableWrite)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        auto var = std::make_shared<Variable>("", "");
+        EXPECT_CALL(*m_builder, createVariableWrite(var.get()));
+        compiler->createVariableWrite(var.get());
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, MoveToIf)
 {
     Compiler compiler(&m_engine, &m_target);

From 10b55f5524658a0a8048eda5f431831ba970a473 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 11:15:23 +0100
Subject: [PATCH 028/259] Compiler: Fix addListContents test name

---
 test/dev/compiler/compiler_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index d8cf8207..e20ab74a 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -116,7 +116,7 @@ TEST_F(CompilerTest, AddVariableValue)
     compile(compiler, block);
 }
 
-TEST_F(CompilerTest, AddListValue)
+TEST_F(CompilerTest, AddListContents)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");

From d2ebe1e2fbcd528d91fc32f1af3bbcad48397206 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 11:42:20 +0100
Subject: [PATCH 029/259] Move LLVM classes to llvm folder

---
 src/dev/engine/CMakeLists.txt                        | 12 ++++++------
 src/dev/engine/internal/codebuilderfactory.cpp       |  2 +-
 .../engine/internal/{ => llvm}/llvmcodebuilder.cpp   |  0
 src/dev/engine/internal/{ => llvm}/llvmcodebuilder.h |  2 +-
 .../internal/{ => llvm}/llvmexecutablecode.cpp       |  0
 .../engine/internal/{ => llvm}/llvmexecutablecode.h  |  0
 .../internal/{ => llvm}/llvmexecutioncontext.cpp     |  0
 .../internal/{ => llvm}/llvmexecutioncontext.h       |  0
 test/dev/llvm/llvmcodebuilder_test.cpp               |  2 +-
 test/dev/llvm/llvmexecutablecode_test.cpp            |  4 ++--
 test/dev/llvm/llvmexecutioncontext_test.cpp          |  2 +-
 11 files changed, 12 insertions(+), 12 deletions(-)
 rename src/dev/engine/internal/{ => llvm}/llvmcodebuilder.cpp (100%)
 rename src/dev/engine/internal/{ => llvm}/llvmcodebuilder.h (99%)
 rename src/dev/engine/internal/{ => llvm}/llvmexecutablecode.cpp (100%)
 rename src/dev/engine/internal/{ => llvm}/llvmexecutablecode.h (100%)
 rename src/dev/engine/internal/{ => llvm}/llvmexecutioncontext.cpp (100%)
 rename src/dev/engine/internal/{ => llvm}/llvmexecutioncontext.h (100%)

diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index 0f4018fd..edce2cc2 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -10,10 +10,10 @@ target_sources(scratchcpp
     internal/icodebuilderfactory.h
     internal/codebuilderfactory.cpp
     internal/codebuilderfactory.h
-    internal/llvmcodebuilder.cpp
-    internal/llvmcodebuilder.h
-    internal/llvmexecutablecode.cpp
-    internal/llvmexecutablecode.h
-    internal/llvmexecutioncontext.cpp
-    internal/llvmexecutioncontext.h
+    internal/llvm/llvmcodebuilder.cpp
+    internal/llvm/llvmcodebuilder.h
+    internal/llvm/llvmexecutablecode.cpp
+    internal/llvm/llvmexecutablecode.h
+    internal/llvm/llvmexecutioncontext.cpp
+    internal/llvm/llvmexecutioncontext.h
 )
diff --git a/src/dev/engine/internal/codebuilderfactory.cpp b/src/dev/engine/internal/codebuilderfactory.cpp
index dfa048ee..999a740a 100644
--- a/src/dev/engine/internal/codebuilderfactory.cpp
+++ b/src/dev/engine/internal/codebuilderfactory.cpp
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include "codebuilderfactory.h"
-#include "llvmcodebuilder.h"
+#include "llvm/llvmcodebuilder.h"
 
 using namespace libscratchcpp;
 
diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
similarity index 100%
rename from src/dev/engine/internal/llvmcodebuilder.cpp
rename to src/dev/engine/internal/llvm/llvmcodebuilder.cpp
diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
similarity index 99%
rename from src/dev/engine/internal/llvmcodebuilder.h
rename to src/dev/engine/internal/llvm/llvmcodebuilder.h
index 704d82da..a8367efd 100644
--- a/src/dev/engine/internal/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -7,7 +7,7 @@
 #include <llvm/IR/Module.h>
 #include <llvm/IR/IRBuilder.h>
 
-#include "icodebuilder.h"
+#include "../icodebuilder.h"
 
 namespace libscratchcpp
 {
diff --git a/src/dev/engine/internal/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
similarity index 100%
rename from src/dev/engine/internal/llvmexecutablecode.cpp
rename to src/dev/engine/internal/llvm/llvmexecutablecode.cpp
diff --git a/src/dev/engine/internal/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h
similarity index 100%
rename from src/dev/engine/internal/llvmexecutablecode.h
rename to src/dev/engine/internal/llvm/llvmexecutablecode.h
diff --git a/src/dev/engine/internal/llvmexecutioncontext.cpp b/src/dev/engine/internal/llvm/llvmexecutioncontext.cpp
similarity index 100%
rename from src/dev/engine/internal/llvmexecutioncontext.cpp
rename to src/dev/engine/internal/llvm/llvmexecutioncontext.cpp
diff --git a/src/dev/engine/internal/llvmexecutioncontext.h b/src/dev/engine/internal/llvm/llvmexecutioncontext.h
similarity index 100%
rename from src/dev/engine/internal/llvmexecutioncontext.h
rename to src/dev/engine/internal/llvm/llvmexecutioncontext.h
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 3c284571..b0597e2a 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -3,7 +3,7 @@
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/stage.h>
 #include <scratchcpp/variable.h>
-#include <dev/engine/internal/llvmcodebuilder.h>
+#include <dev/engine/internal/llvm/llvmcodebuilder.h>
 #include <gmock/gmock.h>
 #include <targetmock.h>
 #include <enginemock.h>
diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp
index 525f3ef3..8af37328 100644
--- a/test/dev/llvm/llvmexecutablecode_test.cpp
+++ b/test/dev/llvm/llvmexecutablecode_test.cpp
@@ -1,7 +1,7 @@
 #include <scratchcpp/target.h>
 #include <scratchcpp/value_functions.h>
-#include <dev/engine/internal/llvmexecutablecode.h>
-#include <dev/engine/internal/llvmexecutioncontext.h>
+#include <dev/engine/internal/llvm/llvmexecutablecode.h>
+#include <dev/engine/internal/llvm/llvmexecutioncontext.h>
 #include <llvm/Support/TargetSelect.h>
 #include <llvm/IR/IRBuilder.h>
 #include <gmock/gmock.h>
diff --git a/test/dev/llvm/llvmexecutioncontext_test.cpp b/test/dev/llvm/llvmexecutioncontext_test.cpp
index 8a9d3e4b..afa6a449 100644
--- a/test/dev/llvm/llvmexecutioncontext_test.cpp
+++ b/test/dev/llvm/llvmexecutioncontext_test.cpp
@@ -1,5 +1,5 @@
 #include <scratchcpp/target.h>
-#include <dev/engine/internal/llvmexecutioncontext.h>
+#include <dev/engine/internal/llvm/llvmexecutioncontext.h>
 #include <gtest/gtest.h>
 
 using namespace libscratchcpp;

From 21ed739b0a955f9e23ed8ef3c18d3c8e387a47a2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 13:48:59 +0100
Subject: [PATCH 030/259] Move structs out of LLVMCodeBuilder

---
 src/dev/engine/CMakeLists.txt                 |   7 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 260 +++++++++---------
 .../engine/internal/llvm/llvmcodebuilder.h    | 136 +--------
 src/dev/engine/internal/llvm/llvmcoroutine.h  |  25 ++
 .../engine/internal/llvm/llvmifstatement.h    |  25 ++
 .../engine/internal/llvm/llvminstruction.h    |  69 +++++
 src/dev/engine/internal/llvm/llvmloop.h       |  24 ++
 src/dev/engine/internal/llvm/llvmprocedure.h  |  14 +
 src/dev/engine/internal/llvm/llvmregister.h   |  34 +++
 .../engine/internal/llvm/llvmvariableptr.h    |  25 ++
 10 files changed, 368 insertions(+), 251 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmcoroutine.h
 create mode 100644 src/dev/engine/internal/llvm/llvmifstatement.h
 create mode 100644 src/dev/engine/internal/llvm/llvminstruction.h
 create mode 100644 src/dev/engine/internal/llvm/llvmloop.h
 create mode 100644 src/dev/engine/internal/llvm/llvmprocedure.h
 create mode 100644 src/dev/engine/internal/llvm/llvmregister.h
 create mode 100644 src/dev/engine/internal/llvm/llvmvariableptr.h

diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index edce2cc2..3ac7ab82 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -12,6 +12,13 @@ target_sources(scratchcpp
     internal/codebuilderfactory.h
     internal/llvm/llvmcodebuilder.cpp
     internal/llvm/llvmcodebuilder.h
+    internal/llvm/llvmregister.h
+    internal/llvm/llvminstruction.h
+    internal/llvm/llvmifstatement.h
+    internal/llvm/llvmloop.h
+    internal/llvm/llvmcoroutine.h
+    internal/llvm/llvmvariableptr.h
+    internal/llvm/llvmprocedure.h
     internal/llvm/llvmexecutablecode.cpp
     internal/llvm/llvmexecutablecode.h
     internal/llvm/llvmexecutioncontext.cpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 96a38b22..aa22589c 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -11,6 +11,8 @@
 
 #include "llvmcodebuilder.h"
 #include "llvmexecutablecode.h"
+#include "llvmifstatement.h"
+#include "llvmloop.h"
 
 using namespace libscratchcpp;
 
@@ -39,9 +41,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 {
     // Do not create coroutine if there are no yield instructions
     if (!m_warp) {
-        auto it = std::find_if(m_steps.begin(), m_steps.end(), [](const Step &step) { return step.type == Step::Type::Yield; });
+        auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const LLVMInstruction &step) { return step.type == LLVMInstruction::Type::Yield; });
 
-        if (it == m_steps.end())
+        if (it == m_instructions.end())
             m_warp = true;
     }
 
@@ -57,13 +59,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_builder.SetInsertPoint(entry);
 
     // Init coroutine
-    Coroutine coro;
+    LLVMCoroutine coro;
 
     if (!m_warp)
         coro = initCoroutine(func);
 
-    std::vector<IfStatement> ifStatements;
-    std::vector<Loop> loops;
+    std::vector<LLVMIfStatement> ifStatements;
+    std::vector<LLVMLoop> loops;
     m_heap.clear();
 
     // Create variable pointers
@@ -81,9 +83,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     }
 
     // Execute recorded steps
-    for (const Step &step : m_steps) {
+    for (const LLVMInstruction &step : m_instructions) {
         switch (step.type) {
-            case Step::Type::FunctionCall: {
+            case LLVMInstruction::Type::FunctionCall: {
                 std::vector<llvm::Type *> types;
                 std::vector<llvm::Value *> args;
 
@@ -109,7 +111,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Add: {
+            case LLVMInstruction::Type::Add: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
@@ -119,7 +121,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Sub: {
+            case LLVMInstruction::Type::Sub: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
@@ -129,7 +131,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Mul: {
+            case LLVMInstruction::Type::Mul: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
@@ -139,7 +141,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Div: {
+            case LLVMInstruction::Type::Div: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
@@ -149,7 +151,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::CmpEQ: {
+            case LLVMInstruction::Type::CmpEQ: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0].second;
                 const auto &arg2 = step.args[1].second;
@@ -157,7 +159,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::CmpGT: {
+            case LLVMInstruction::Type::CmpGT: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0].second;
                 const auto &arg2 = step.args[1].second;
@@ -165,7 +167,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::CmpLT: {
+            case LLVMInstruction::Type::CmpLT: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0].second;
                 const auto &arg2 = step.args[1].second;
@@ -173,7 +175,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::And: {
+            case LLVMInstruction::Type::And: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
@@ -183,7 +185,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Or: {
+            case LLVMInstruction::Type::Or: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
@@ -193,7 +195,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Not: {
+            case LLVMInstruction::Type::Not: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 llvm::Value *value = castValue(arg.second, arg.first);
@@ -201,7 +203,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Mod: {
+            case LLVMInstruction::Type::Mod: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
@@ -215,7 +217,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Round: {
+            case LLVMInstruction::Type::Round: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // x >= 0.0 ? round(x) : (x >= -0.5 ? -0.0 : floor(x + 0.5))
@@ -232,7 +234,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Abs: {
+            case LLVMInstruction::Type::Abs: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 llvm::Function *absFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::fabs, m_builder.getDoubleTy());
@@ -241,7 +243,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Floor: {
+            case LLVMInstruction::Type::Floor: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::floor, m_builder.getDoubleTy());
@@ -250,7 +252,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Ceil: {
+            case LLVMInstruction::Type::Ceil: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 llvm::Function *ceilFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::ceil, m_builder.getDoubleTy());
@@ -259,7 +261,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Sqrt: {
+            case LLVMInstruction::Type::Sqrt: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // sqrt(x) + 0.0
@@ -271,7 +273,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Sin: {
+            case LLVMInstruction::Type::Sin: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // round(sin(x * pi / 180.0) * 1e10) / 1e10 + 0.0
@@ -289,7 +291,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Cos: {
+            case LLVMInstruction::Type::Cos: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // round(cos(x * pi / 180.0) * 1e10) / 1e10
@@ -305,7 +307,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Tan: {
+            case LLVMInstruction::Type::Tan: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // ((mod = rem(x, 360.0)) == -270.0 || mod == 90.0) ? inf : ((mod == -90.0 || mod == 270.0) ? -inf : round(tan(x * pi / 180.0) * 1e10) / 1e10 + 0.0)
@@ -337,7 +339,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Asin: {
+            case LLVMInstruction::Type::Asin: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // asin(x) * 180.0 / pi + 0.0
@@ -351,7 +353,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Acos: {
+            case LLVMInstruction::Type::Acos: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // acos(x) * 180.0 / pi
@@ -363,7 +365,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Atan: {
+            case LLVMInstruction::Type::Atan: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // atan(x) * 180.0 / pi
@@ -375,7 +377,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Ln: {
+            case LLVMInstruction::Type::Ln: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // log(x)
@@ -385,7 +387,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Log10: {
+            case LLVMInstruction::Type::Log10: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // log10(x)
@@ -395,7 +397,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Exp: {
+            case LLVMInstruction::Type::Exp: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // exp(x)
@@ -405,7 +407,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::Exp10: {
+            case LLVMInstruction::Type::Exp10: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // exp10(x)
@@ -415,24 +417,24 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::WriteVariable: {
+            case LLVMInstruction::Type::WriteVariable: {
                 assert(step.args.size() == 1);
                 assert(m_variablePtrs.find(step.workVariable) != m_variablePtrs.cend());
                 const auto &arg = step.args[0];
-                VariablePtr &varPtr = m_variablePtrs[step.workVariable];
+                LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
                 varPtr.changed = true;
                 createValueStore(arg.second, varPtr.ptr);
                 break;
             }
 
-            case Step::Type::ReadVariable: {
+            case LLVMInstruction::Type::ReadVariable: {
                 assert(step.args.size() == 0);
-                const VariablePtr &varPtr = m_variablePtrs[step.workVariable];
+                const LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
                 step.functionReturnReg->value = varPtr.ptr;
                 break;
             }
 
-            case Step::Type::Yield:
+            case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
                     syncVariables(targetVariables);
@@ -448,8 +450,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 break;
 
-            case Step::Type::BeginIf: {
-                IfStatement statement;
+            case LLVMInstruction::Type::BeginIf: {
+                LLVMIfStatement statement;
                 statement.beforeIf = m_builder.GetInsertBlock();
                 statement.body = llvm::BasicBlock::Create(m_ctx, "", func);
 
@@ -467,9 +469,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::BeginElse: {
+            case LLVMInstruction::Type::BeginElse: {
                 assert(!ifStatements.empty());
-                IfStatement &statement = ifStatements.back();
+                LLVMIfStatement &statement = ifStatements.back();
 
                 // Jump to the branch after the if statement
                 assert(!statement.afterIf);
@@ -490,9 +492,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::EndIf: {
+            case LLVMInstruction::Type::EndIf: {
                 assert(!ifStatements.empty());
-                IfStatement &statement = ifStatements.back();
+                LLVMIfStatement &statement = ifStatements.back();
 
                 // Jump to the branch after the if statement
                 if (!statement.afterIf)
@@ -515,8 +517,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::BeginRepeatLoop: {
-                Loop loop;
+            case LLVMInstruction::Type::BeginRepeatLoop: {
+                LLVMLoop loop;
                 loop.isRepeatLoop = true;
 
                 // index = 0
@@ -568,9 +570,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::BeginWhileLoop: {
+            case LLVMInstruction::Type::BeginWhileLoop: {
                 assert(!loops.empty());
-                Loop &loop = loops.back();
+                LLVMLoop &loop = loops.back();
 
                 // Create branches
                 llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func);
@@ -589,9 +591,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::BeginRepeatUntilLoop: {
+            case LLVMInstruction::Type::BeginRepeatUntilLoop: {
                 assert(!loops.empty());
-                Loop &loop = loops.back();
+                LLVMLoop &loop = loops.back();
 
                 // Create branches
                 llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func);
@@ -610,8 +612,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::BeginLoopCondition: {
-                Loop loop;
+            case LLVMInstruction::Type::BeginLoopCondition: {
+                LLVMLoop loop;
                 loop.isRepeatLoop = false;
                 loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func);
                 freeHeap();
@@ -621,9 +623,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
-            case Step::Type::EndLoop: {
+            case LLVMInstruction::Type::EndLoop: {
                 assert(!loops.empty());
-                Loop &loop = loops.back();
+                LLVMLoop &loop = loops.back();
 
                 if (loop.isRepeatLoop) {
                     // Increment index
@@ -711,33 +713,33 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
 void LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector<Compiler::StaticType> &argTypes)
 {
-    Step step(Step::Type::FunctionCall);
-    step.functionName = functionName;
+    LLVMInstruction ins(LLVMInstruction::Type::FunctionCall);
+    ins.functionName = functionName;
 
     assert(m_tmpRegs.size() >= argTypes.size());
     size_t j = 0;
 
     for (size_t i = m_tmpRegs.size() - argTypes.size(); i < m_tmpRegs.size(); i++)
-        step.args.push_back({ argTypes[j++], m_tmpRegs[i] });
+        ins.args.push_back({ argTypes[j++], m_tmpRegs[i] });
 
     m_tmpRegs.erase(m_tmpRegs.end() - argTypes.size(), m_tmpRegs.end());
 
-    step.functionReturnType = returnType;
+    ins.functionReturnType = returnType;
 
     if (returnType != Compiler::StaticType::Void) {
-        auto reg = std::make_shared<Register>(returnType);
+        auto reg = std::make_shared<LLVMRegister>(returnType);
         reg->isRawValue = true;
-        step.functionReturnReg = reg;
+        ins.functionReturnReg = reg;
         m_regs[m_currentFunction].push_back(reg);
         m_tmpRegs.push_back(reg);
     }
 
-    m_steps.push_back(step);
+    m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::addConstValue(const Value &value)
 {
-    auto reg = std::make_shared<Register>(TYPE_MAP[value.type()]);
+    auto reg = std::make_shared<LLVMRegister>(TYPE_MAP[value.type()]);
     reg->isConstValue = true;
     reg->constValue = value;
     m_regs[m_currentFunction].push_back(reg);
@@ -747,17 +749,17 @@ void LLVMCodeBuilder::addConstValue(const Value &value)
 void LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
     // TODO: Implement type prediction
-    Step step(Step::Type::ReadVariable);
-    step.workVariable = variable;
-    m_variablePtrs[variable] = VariablePtr();
+    LLVMInstruction ins(LLVMInstruction::Type::ReadVariable);
+    ins.workVariable = variable;
+    m_variablePtrs[variable] = LLVMVariablePtr();
 
-    auto ret = std::make_shared<Register>(Compiler::StaticType::Unknown);
+    auto ret = std::make_shared<LLVMRegister>(Compiler::StaticType::Unknown);
     ret->isRawValue = false;
-    step.functionReturnReg = ret;
+    ins.functionReturnReg = ret;
     m_regs[m_currentFunction].push_back(ret);
     m_tmpRegs.push_back(ret);
 
-    m_steps.push_back(step);
+    m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::addListContents(List *list)
@@ -766,203 +768,203 @@ void LLVMCodeBuilder::addListContents(List *list)
 
 void LLVMCodeBuilder::createAdd()
 {
-    createOp(Step::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createSub()
 {
-    createOp(Step::Type::Sub, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::Sub, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createMul()
 {
-    createOp(Step::Type::Mul, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::Mul, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createDiv()
 {
-    createOp(Step::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createCmpEQ()
 {
-    createOp(Step::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createCmpGT()
 {
-    createOp(Step::Type::CmpGT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::CmpGT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createCmpLT()
 {
-    createOp(Step::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createAnd()
 {
-    createOp(Step::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2);
+    createOp(LLVMInstruction::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2);
 }
 
 void LLVMCodeBuilder::createOr()
 {
-    createOp(Step::Type::Or, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2);
+    createOp(LLVMInstruction::Type::Or, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2);
 }
 
 void LLVMCodeBuilder::createNot()
 {
-    createOp(Step::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 1);
+    createOp(LLVMInstruction::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 1);
 }
 
 void LLVMCodeBuilder::createMod()
 {
-    createOp(Step::Type::Mod, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    createOp(LLVMInstruction::Type::Mod, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
 }
 
 void LLVMCodeBuilder::createRound()
 {
-    createOp(Step::Type::Round, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Round, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createAbs()
 {
-    createOp(Step::Type::Abs, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Abs, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createFloor()
 {
-    createOp(Step::Type::Floor, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Floor, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createCeil()
 {
-    createOp(Step::Type::Ceil, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Ceil, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createSqrt()
 {
-    createOp(Step::Type::Sqrt, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Sqrt, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createSin()
 {
-    createOp(Step::Type::Sin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Sin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createCos()
 {
-    createOp(Step::Type::Cos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Cos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createTan()
 {
-    createOp(Step::Type::Tan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Tan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createAsin()
 {
-    createOp(Step::Type::Asin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Asin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createAcos()
 {
-    createOp(Step::Type::Acos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Acos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createAtan()
 {
-    createOp(Step::Type::Atan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Atan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createLn()
 {
-    createOp(Step::Type::Ln, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Ln, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createLog10()
 {
-    createOp(Step::Type::Log10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Log10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createExp()
 {
-    createOp(Step::Type::Exp, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Exp, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createExp10()
 {
-    createOp(Step::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    createOp(LLVMInstruction::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
 }
 
 void LLVMCodeBuilder::createVariableWrite(Variable *variable)
 {
-    Step &step = createOp(Step::Type::WriteVariable, Compiler::StaticType::Void, Compiler::StaticType::Unknown, 1);
-    step.workVariable = variable;
-    m_variablePtrs[variable] = VariablePtr();
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::WriteVariable, Compiler::StaticType::Void, Compiler::StaticType::Unknown, 1);
+    ins.workVariable = variable;
+    m_variablePtrs[variable] = LLVMVariablePtr();
 }
 
 void LLVMCodeBuilder::beginIfStatement()
 {
-    Step step(Step::Type::BeginIf);
+    LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
     assert(!m_tmpRegs.empty());
-    step.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
+    ins.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
     m_tmpRegs.pop_back();
-    m_steps.push_back(step);
+    m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::beginElseBranch()
 {
-    m_steps.push_back(Step(Step::Type::BeginElse));
+    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::BeginElse));
 }
 
 void LLVMCodeBuilder::endIf()
 {
-    m_steps.push_back(Step(Step::Type::EndIf));
+    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndIf));
 }
 
 void LLVMCodeBuilder::beginRepeatLoop()
 {
-    Step step(Step::Type::BeginRepeatLoop);
+    LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatLoop);
     assert(!m_tmpRegs.empty());
-    step.args.push_back({ Compiler::StaticType::Number, m_tmpRegs.back() });
+    ins.args.push_back({ Compiler::StaticType::Number, m_tmpRegs.back() });
     m_tmpRegs.pop_back();
-    m_steps.push_back(step);
+    m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::beginWhileLoop()
 {
-    Step step(Step::Type::BeginWhileLoop);
+    LLVMInstruction ins(LLVMInstruction::Type::BeginWhileLoop);
     assert(!m_tmpRegs.empty());
-    step.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
+    ins.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
     m_tmpRegs.pop_back();
-    m_steps.push_back(step);
+    m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::beginRepeatUntilLoop()
 {
-    Step step(Step::Type::BeginRepeatUntilLoop);
+    LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatUntilLoop);
     assert(!m_tmpRegs.empty());
-    step.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
+    ins.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
     m_tmpRegs.pop_back();
-    m_steps.push_back(step);
+    m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::beginLoopCondition()
 {
-    m_steps.push_back(Step(Step::Type::BeginLoopCondition));
+    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::BeginLoopCondition));
 }
 
 void LLVMCodeBuilder::endLoop()
 {
     if (!m_warp)
-        m_steps.push_back(Step(Step::Type::Yield));
+        m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::Yield));
 
-    m_steps.push_back(Step(Step::Type::EndLoop));
+    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndLoop));
 }
 
 void LLVMCodeBuilder::yield()
 {
-    m_steps.push_back({ Step::Type::Yield });
+    m_instructions.push_back({ LLVMInstruction::Type::Yield });
     m_currentFunction++;
 
     assert(m_currentFunction == m_constValues.size());
@@ -1014,7 +1016,7 @@ void LLVMCodeBuilder::createVariableMap()
     }
 }
 
-LLVMCodeBuilder::Coroutine LLVMCodeBuilder::initCoroutine(llvm::Function *func)
+LLVMCoroutine LLVMCodeBuilder::initCoroutine(llvm::Function *func)
 {
     // Set presplitcoroutine attribute
     func->setPresplitCoroutine();
@@ -1027,7 +1029,7 @@ LLVMCodeBuilder::Coroutine LLVMCodeBuilder::initCoroutine(llvm::Function *func)
     llvm::Function *coroFree = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_free);
 
     // Init coroutine
-    Coroutine coro;
+    LLVMCoroutine coro;
     llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
     llvm::Constant *nullPointer = llvm::ConstantPointerNull::get(pointerType);
     llvm::Value *coroIdRet = m_builder.CreateCall(coroId, { m_builder.getInt32(8), nullPointer, nullPointer, nullPointer });
@@ -1106,7 +1108,7 @@ void LLVMCodeBuilder::freeHeap()
     m_heap.clear();
 }
 
-llvm::Value *LLVMCodeBuilder::castValue(std::shared_ptr<Register> reg, Compiler::StaticType targetType)
+llvm::Value *LLVMCodeBuilder::castValue(LLVMRegisterPtr reg, Compiler::StaticType targetType)
 {
     if (reg->isConstValue)
         return castConstValue(reg->constValue, targetType);
@@ -1196,7 +1198,7 @@ llvm::Value *LLVMCodeBuilder::castValue(std::shared_ptr<Register> reg, Compiler:
     }
 }
 
-llvm::Value *LLVMCodeBuilder::castRawValue(std::shared_ptr<Register> reg, Compiler::StaticType targetType)
+llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegisterPtr reg, Compiler::StaticType targetType)
 {
     if (reg->type == targetType)
         return reg->value;
@@ -1337,31 +1339,31 @@ void LLVMCodeBuilder::syncVariables(llvm::Value *targetVariables)
     }
 }
 
-LLVMCodeBuilder::Step &LLVMCodeBuilder::createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
+LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
 {
-    Step step(type);
+    LLVMInstruction ins(type);
 
     assert(m_tmpRegs.size() >= argCount);
     size_t j = 0;
 
     for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++)
-        step.args.push_back({ argType, m_tmpRegs[i] });
+        ins.args.push_back({ argType, m_tmpRegs[i] });
 
     m_tmpRegs.erase(m_tmpRegs.end() - argCount, m_tmpRegs.end());
 
     if (retType != Compiler::StaticType::Void) {
-        auto ret = std::make_shared<Register>(retType);
+        auto ret = std::make_shared<LLVMRegister>(retType);
         ret->isRawValue = true;
-        step.functionReturnReg = ret;
+        ins.functionReturnReg = ret;
         m_regs[m_currentFunction].push_back(ret);
         m_tmpRegs.push_back(ret);
     }
 
-    m_steps.push_back(step);
-    return m_steps.back();
+    m_instructions.push_back(ins);
+    return m_instructions.back();
 }
 
-void LLVMCodeBuilder::createValueStore(std::shared_ptr<Register> reg, llvm::Value *targetPtr)
+void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr)
 {
     // TODO: Implement type prediction
     Compiler::StaticType type = reg->type;
@@ -1416,7 +1418,7 @@ void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target,
     m_builder.CreateStore(m_builder.CreateLoad(fieldType, sourceField), targetField);
 }
 
-llvm::Value *LLVMCodeBuilder::createValue(std::shared_ptr<Register> reg)
+llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
 {
     if (reg->isConstValue) {
         // Create a constant ValueData instance and store it
@@ -1458,7 +1460,7 @@ llvm::Value *LLVMCodeBuilder::createValue(std::shared_ptr<Register> reg)
         return reg->value;
 }
 
-llvm::Value *LLVMCodeBuilder::createComparison(std::shared_ptr<Register> arg1, std::shared_ptr<Register> arg2, Comparison type)
+llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type)
 {
     auto type1 = arg1->type;
     auto type2 = arg2->type;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index a8367efd..db6a4243 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -8,6 +8,9 @@
 #include <llvm/IR/IRBuilder.h>
 
 #include "../icodebuilder.h"
+#include "llvminstruction.h"
+#include "llvmcoroutine.h"
+#include "llvmvariableptr.h"
 
 namespace libscratchcpp
 {
@@ -71,117 +74,6 @@ class LLVMCodeBuilder : public ICodeBuilder
         void yield() override;
 
     private:
-        struct Register
-        {
-                Register(Compiler::StaticType type) :
-                    type(type)
-                {
-                }
-
-                Compiler::StaticType type = Compiler::StaticType::Void;
-                llvm::Value *value = nullptr;
-                bool isRawValue = false;
-                bool isConstValue = false;
-                Value constValue;
-        };
-
-        struct Step
-        {
-                enum class Type
-                {
-                    FunctionCall,
-                    Add,
-                    Sub,
-                    Mul,
-                    Div,
-                    CmpEQ,
-                    CmpGT,
-                    CmpLT,
-                    And,
-                    Or,
-                    Not,
-                    Mod,
-                    Round,
-                    Abs,
-                    Floor,
-                    Ceil,
-                    Sqrt,
-                    Sin,
-                    Cos,
-                    Tan,
-                    Asin,
-                    Acos,
-                    Atan,
-                    Ln,
-                    Log10,
-                    Exp,
-                    Exp10,
-                    WriteVariable,
-                    ReadVariable,
-                    Yield,
-                    BeginIf,
-                    BeginElse,
-                    EndIf,
-                    BeginRepeatLoop,
-                    BeginWhileLoop,
-                    BeginRepeatUntilLoop,
-                    BeginLoopCondition,
-                    EndLoop
-                };
-
-                Step(Type type) :
-                    type(type)
-                {
-                }
-
-                Type type;
-                std::string functionName;
-                std::vector<std::pair<Compiler::StaticType, std::shared_ptr<Register>>> args; // target type, register
-                Compiler::StaticType functionReturnType = Compiler::StaticType::Void;
-                std::shared_ptr<Register> functionReturnReg;
-                Variable *workVariable = nullptr; // for variable write
-        };
-
-        struct IfStatement
-        {
-                llvm::Value *condition = nullptr;
-                llvm::BasicBlock *beforeIf = nullptr;
-                llvm::BasicBlock *body = nullptr;
-                llvm::BasicBlock *elseBranch = nullptr;
-                llvm::BasicBlock *afterIf = nullptr;
-        };
-
-        struct Loop
-        {
-                bool isRepeatLoop = false;
-                llvm::Value *index = nullptr;
-                llvm::BasicBlock *conditionBranch = nullptr;
-                llvm::BasicBlock *afterLoop = nullptr;
-        };
-
-        struct Coroutine
-        {
-                llvm::Value *handle = nullptr;
-                llvm::BasicBlock *suspend = nullptr;
-                llvm::BasicBlock *cleanup = nullptr;
-                llvm::BasicBlock *freeMemRet = nullptr;
-                llvm::Value *didSuspend = nullptr;
-        };
-
-        struct VariablePtr
-        {
-                llvm::Value *ptr = nullptr;
-                Compiler::StaticType type = Compiler::StaticType::Unknown;
-                bool onStack = false;
-                bool changed = false;
-        };
-
-        struct Procedure
-        {
-                // TODO: Implement procedures
-                bool warp = false;
-        };
-
         enum class Comparison
         {
             EQ,
@@ -192,13 +84,13 @@ class LLVMCodeBuilder : public ICodeBuilder
         void initTypes();
         void createVariableMap();
 
-        Coroutine initCoroutine(llvm::Function *func);
+        LLVMCoroutine initCoroutine(llvm::Function *func);
         void verifyFunction(llvm::Function *func);
         void optimize();
 
         void freeHeap();
-        llvm::Value *castValue(std::shared_ptr<Register> reg, Compiler::StaticType targetType);
-        llvm::Value *castRawValue(std::shared_ptr<Register> reg, Compiler::StaticType targetType);
+        llvm::Value *castValue(LLVMRegisterPtr reg, Compiler::StaticType targetType);
+        llvm::Value *castRawValue(LLVMRegisterPtr reg, Compiler::StaticType targetType);
         llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType);
         llvm::Type *getType(Compiler::StaticType type);
         llvm::Value *isNaN(llvm::Value *num);
@@ -207,13 +99,13 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *getVariablePtr(llvm::Value *targetVariables, Variable *variable);
         void syncVariables(llvm::Value *targetVariables);
 
-        Step &createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
+        LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
 
-        void createValueStore(std::shared_ptr<Register> reg, llvm::Value *targetPtr);
+        void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
-        llvm::Value *createValue(std::shared_ptr<Register> reg);
-        llvm::Value *createComparison(std::shared_ptr<Register> arg1, std::shared_ptr<Register> arg2, Comparison type);
+        llvm::Value *createValue(LLVMRegisterPtr reg);
+        llvm::Value *createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type);
 
         llvm::FunctionCallee resolveFunction(const std::string name, llvm::FunctionType *type);
         llvm::FunctionCallee resolve_value_init();
@@ -238,7 +130,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         Target *m_target = nullptr;
         std::unordered_map<Variable *, size_t> m_targetVariableMap;
-        std::unordered_map<Variable *, VariablePtr> m_variablePtrs;
+        std::unordered_map<Variable *, LLVMVariablePtr> m_variablePtrs;
 
         std::string m_id;
         llvm::LLVMContext m_ctx;
@@ -247,11 +139,11 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         llvm::StructType *m_valueDataType = nullptr;
 
-        std::vector<Step> m_steps;
+        std::vector<LLVMInstruction> m_instructions;
         size_t m_currentFunction = 0;
         std::vector<Value> m_constValues;
-        std::vector<std::vector<std::shared_ptr<Register>>> m_regs;
-        std::vector<std::shared_ptr<Register>> m_tmpRegs;
+        std::vector<std::vector<LLVMRegisterPtr>> m_regs;
+        std::vector<LLVMRegisterPtr> m_tmpRegs;
         bool m_defaultWarp = false;
         bool m_warp = false;
 
diff --git a/src/dev/engine/internal/llvm/llvmcoroutine.h b/src/dev/engine/internal/llvm/llvmcoroutine.h
new file mode 100644
index 00000000..c3efb0dc
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmcoroutine.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace llvm
+{
+
+class Value;
+class BasicBlock;
+
+} // namespace llvm
+
+namespace libscratchcpp
+{
+
+struct LLVMCoroutine
+{
+        llvm::Value *handle = nullptr;
+        llvm::BasicBlock *suspend = nullptr;
+        llvm::BasicBlock *cleanup = nullptr;
+        llvm::BasicBlock *freeMemRet = nullptr;
+        llvm::Value *didSuspend = nullptr;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmifstatement.h b/src/dev/engine/internal/llvm/llvmifstatement.h
new file mode 100644
index 00000000..83ab9608
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmifstatement.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace llvm
+{
+
+class Value;
+class BasicBlock;
+
+} // namespace llvm
+
+namespace libscratchcpp
+{
+
+struct LLVMIfStatement
+{
+        llvm::Value *condition = nullptr;
+        llvm::BasicBlock *beforeIf = nullptr;
+        llvm::BasicBlock *body = nullptr;
+        llvm::BasicBlock *elseBranch = nullptr;
+        llvm::BasicBlock *afterIf = nullptr;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
new file mode 100644
index 00000000..81fed9cd
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/dev/compiler.h>
+
+#include "llvmregister.h"
+
+namespace libscratchcpp
+{
+
+struct LLVMInstruction
+{
+        enum class Type
+        {
+            FunctionCall,
+            Add,
+            Sub,
+            Mul,
+            Div,
+            CmpEQ,
+            CmpGT,
+            CmpLT,
+            And,
+            Or,
+            Not,
+            Mod,
+            Round,
+            Abs,
+            Floor,
+            Ceil,
+            Sqrt,
+            Sin,
+            Cos,
+            Tan,
+            Asin,
+            Acos,
+            Atan,
+            Ln,
+            Log10,
+            Exp,
+            Exp10,
+            WriteVariable,
+            ReadVariable,
+            Yield,
+            BeginIf,
+            BeginElse,
+            EndIf,
+            BeginRepeatLoop,
+            BeginWhileLoop,
+            BeginRepeatUntilLoop,
+            BeginLoopCondition,
+            EndLoop
+        };
+
+        LLVMInstruction(Type type) :
+            type(type)
+        {
+        }
+
+        Type type;
+        std::string functionName;
+        std::vector<std::pair<Compiler::StaticType, LLVMRegisterPtr>> args; // target type, register
+        Compiler::StaticType functionReturnType = Compiler::StaticType::Void;
+        LLVMRegisterPtr functionReturnReg;
+        Variable *workVariable = nullptr; // for variables
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmloop.h b/src/dev/engine/internal/llvm/llvmloop.h
new file mode 100644
index 00000000..3747b39b
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmloop.h
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace llvm
+{
+
+class Value;
+class BasicBlock;
+
+} // namespace llvm
+
+namespace libscratchcpp
+{
+
+struct LLVMLoop
+{
+        bool isRepeatLoop = false;
+        llvm::Value *index = nullptr;
+        llvm::BasicBlock *conditionBranch = nullptr;
+        llvm::BasicBlock *afterLoop = nullptr;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmprocedure.h b/src/dev/engine/internal/llvm/llvmprocedure.h
new file mode 100644
index 00000000..7cb57679
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmprocedure.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace libscratchcpp
+{
+
+struct LLVMProcedure
+{
+        // TODO: Implement procedures
+        bool warp = false;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmregister.h b/src/dev/engine/internal/llvm/llvmregister.h
new file mode 100644
index 00000000..96e4dee3
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmregister.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/value.h>
+
+namespace llvm
+{
+
+class Value;
+
+}
+
+namespace libscratchcpp
+{
+
+struct LLVMRegister
+{
+        LLVMRegister(Compiler::StaticType type) :
+            type(type)
+        {
+        }
+
+        Compiler::StaticType type = Compiler::StaticType::Void;
+        llvm::Value *value = nullptr;
+        bool isRawValue = false;
+        bool isConstValue = false;
+        Value constValue;
+};
+
+using LLVMRegisterPtr = std::shared_ptr<LLVMRegister>;
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmvariableptr.h b/src/dev/engine/internal/llvm/llvmvariableptr.h
new file mode 100644
index 00000000..bfc65e3f
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmvariableptr.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/dev/compiler.h>
+
+namespace llvm
+{
+
+class Value;
+
+}
+
+namespace libscratchcpp
+{
+
+struct LLVMVariablePtr
+{
+        llvm::Value *ptr = nullptr;
+        Compiler::StaticType type = Compiler::StaticType::Unknown;
+        bool onStack = false;
+        bool changed = false;
+};
+
+} // namespace libscratchcpp

From ec698eb9d1b22b66ba0eb69bfa7575ec7e3e65c3 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 13:52:52 +0100
Subject: [PATCH 031/259] Add CMakeLists.txt to llvm folder

---
 src/dev/engine/CMakeLists.txt               | 15 ++-------------
 src/dev/engine/internal/llvm/CMakeLists.txt | 16 ++++++++++++++++
 2 files changed, 18 insertions(+), 13 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/CMakeLists.txt

diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index 3ac7ab82..61d172da 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -10,17 +10,6 @@ target_sources(scratchcpp
     internal/icodebuilderfactory.h
     internal/codebuilderfactory.cpp
     internal/codebuilderfactory.h
-    internal/llvm/llvmcodebuilder.cpp
-    internal/llvm/llvmcodebuilder.h
-    internal/llvm/llvmregister.h
-    internal/llvm/llvminstruction.h
-    internal/llvm/llvmifstatement.h
-    internal/llvm/llvmloop.h
-    internal/llvm/llvmcoroutine.h
-    internal/llvm/llvmvariableptr.h
-    internal/llvm/llvmprocedure.h
-    internal/llvm/llvmexecutablecode.cpp
-    internal/llvm/llvmexecutablecode.h
-    internal/llvm/llvmexecutioncontext.cpp
-    internal/llvm/llvmexecutioncontext.h
 )
+
+add_subdirectory(internal/llvm)
diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
new file mode 100644
index 00000000..7cef960d
--- /dev/null
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -0,0 +1,16 @@
+target_sources(scratchcpp
+  PRIVATE
+    llvmcodebuilder.cpp
+    llvmcodebuilder.h
+    llvmregister.h
+    llvminstruction.h
+    llvmifstatement.h
+    llvmloop.h
+    llvmcoroutine.h
+    llvmvariableptr.h
+    llvmprocedure.h
+    llvmexecutablecode.cpp
+    llvmexecutablecode.h
+    llvmexecutioncontext.cpp
+    llvmexecutioncontext.h
+)

From feb8a0b19da580348651f50d9adb1f015579b96e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 14:03:01 +0100
Subject: [PATCH 032/259] Add LLVMTypes class

---
 src/dev/engine/internal/llvm/CMakeLists.txt   |  2 ++
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 10 ++------
 src/dev/engine/internal/llvm/llvmtypes.cpp    | 23 +++++++++++++++++++
 src/dev/engine/internal/llvm/llvmtypes.h      | 23 +++++++++++++++++++
 4 files changed, 50 insertions(+), 8 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmtypes.cpp
 create mode 100644 src/dev/engine/internal/llvm/llvmtypes.h

diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index 7cef960d..20a71230 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -9,6 +9,8 @@ target_sources(scratchcpp
     llvmcoroutine.h
     llvmvariableptr.h
     llvmprocedure.h
+    llvmtypes.cpp
+    llvmtypes.h
     llvmexecutablecode.cpp
     llvmexecutablecode.h
     llvmexecutioncontext.cpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index aa22589c..e387a3bb 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -13,6 +13,7 @@
 #include "llvmexecutablecode.h"
 #include "llvmifstatement.h"
 #include "llvmloop.h"
+#include "llvmtypes.h"
 
 using namespace libscratchcpp;
 
@@ -976,14 +977,7 @@ void LLVMCodeBuilder::yield()
 
 void LLVMCodeBuilder::initTypes()
 {
-    // Create the ValueData struct
-    llvm::Type *unionType = m_builder.getInt64Ty(); // 64 bits is the largest size
-
-    llvm::Type *valueType = llvm::Type::getInt32Ty(m_ctx); // Assuming ValueType is a 32-bit enum
-    llvm::Type *sizeType = llvm::Type::getInt64Ty(m_ctx);  // size_t
-
-    m_valueDataType = llvm::StructType::create(m_ctx, "ValueData");
-    m_valueDataType->setBody({ unionType, valueType, sizeType });
+    m_valueDataType = LLVMTypes::createValueDataType(&m_builder);
 }
 
 void LLVMCodeBuilder::createVariableMap()
diff --git a/src/dev/engine/internal/llvm/llvmtypes.cpp b/src/dev/engine/internal/llvm/llvmtypes.cpp
new file mode 100644
index 00000000..bbd21f28
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmtypes.cpp
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <llvm/IR/IRBuilder.h>
+
+#include "llvmtypes.h"
+
+using namespace libscratchcpp;
+
+llvm::StructType *LLVMTypes::createValueDataType(llvm::IRBuilder<> *builder)
+{
+    llvm::LLVMContext &ctx = builder->getContext();
+
+    // Create the ValueData struct
+    llvm::Type *unionType = builder->getInt64Ty(); // 64 bits is the largest size
+
+    llvm::Type *valueType = llvm::Type::getInt32Ty(ctx); // Assuming ValueType is a 32-bit enum
+    llvm::Type *sizeType = llvm::Type::getInt64Ty(ctx);  // size_t
+
+    llvm::StructType *ret = llvm::StructType::create(ctx, "ValueData");
+    ret->setBody({ unionType, valueType, sizeType });
+
+    return ret;
+}
diff --git a/src/dev/engine/internal/llvm/llvmtypes.h b/src/dev/engine/internal/llvm/llvmtypes.h
new file mode 100644
index 00000000..ae0e3c85
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmtypes.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <llvm/IR/IRBuilder.h>
+
+namespace llvm
+{
+
+class StructValue;
+
+}
+
+namespace libscratchcpp
+{
+
+class LLVMTypes
+{
+    public:
+        static llvm::StructType *createValueDataType(llvm::IRBuilder<> *builder);
+};
+
+} // namespace libscratchcpp

From 3b4151776c3fe9b627cb0a46351897936b02fc01 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 15:00:09 +0100
Subject: [PATCH 033/259] Move coroutine code out of LLVMCodeBuilder

---
 src/dev/engine/internal/llvm/CMakeLists.txt   |   1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 104 ++------------
 .../engine/internal/llvm/llvmcodebuilder.h    |   1 -
 .../engine/internal/llvm/llvmcoroutine.cpp    | 130 ++++++++++++++++++
 src/dev/engine/internal/llvm/llvmcoroutine.h  |  40 ++++--
 5 files changed, 168 insertions(+), 108 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmcoroutine.cpp

diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index 20a71230..a7833f5b 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -6,6 +6,7 @@ target_sources(scratchcpp
     llvminstruction.h
     llvmifstatement.h
     llvmloop.h
+    llvmcoroutine.cpp
     llvmcoroutine.h
     llvmvariableptr.h
     llvmprocedure.h
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index e387a3bb..f0a42520 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -60,10 +60,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_builder.SetInsertPoint(entry);
 
     // Init coroutine
-    LLVMCoroutine coro;
+    std::unique_ptr<LLVMCoroutine> coro;
 
     if (!m_warp)
-        coro = initCoroutine(func);
+        coro = std::make_unique<LLVMCoroutine>(m_module.get(), &m_builder, func);
 
     std::vector<LLVMIfStatement> ifStatements;
     std::vector<LLVMLoop> loops;
@@ -439,14 +439,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 if (!m_warp) {
                     freeHeap();
                     syncVariables(targetVariables);
-                    m_builder.CreateStore(m_builder.getInt1(true), coro.didSuspend);
-                    llvm::BasicBlock *resumeBranch = llvm::BasicBlock::Create(m_ctx, "", func);
-                    llvm::Value *noneToken = llvm::ConstantTokenNone::get(m_ctx);
-                    llvm::Value *suspendResult = m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_suspend), { noneToken, m_builder.getInt1(false) });
-                    llvm::SwitchInst *sw = m_builder.CreateSwitch(suspendResult, coro.suspend, 2);
-                    sw->addCase(m_builder.getInt8(0), resumeBranch);
-                    sw->addCase(m_builder.getInt8(1), coro.cleanup);
-                    m_builder.SetInsertPoint(resumeBranch);
+                    coro->createSuspend();
                 }
 
                 break;
@@ -651,34 +644,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     freeHeap();
     syncVariables(targetVariables);
 
-    // Add final suspend point
-    if (!m_warp) {
-        llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_ctx, "end", func);
-        llvm::BasicBlock *finalSuspendBranch = llvm::BasicBlock::Create(m_ctx, "finalSuspend", func);
-        m_builder.CreateCondBr(m_builder.CreateLoad(m_builder.getInt1Ty(), coro.didSuspend), finalSuspendBranch, endBranch);
-
-        m_builder.SetInsertPoint(finalSuspendBranch);
-        llvm::Value *suspendResult =
-            m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_suspend), { llvm::ConstantTokenNone::get(m_ctx), m_builder.getInt1(true) });
-        llvm::SwitchInst *sw = m_builder.CreateSwitch(suspendResult, coro.suspend, 2);
-        sw->addCase(m_builder.getInt8(0), endBranch); // unreachable
-        sw->addCase(m_builder.getInt8(1), coro.cleanup);
-
-        m_builder.SetInsertPoint(endBranch);
-    }
-
     // End and verify the function
+    if (m_warp)
+        m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType));
+    else
+        coro->end();
+
     if (!m_tmpRegs.empty()) {
         std::cout
             << "warning: " << m_tmpRegs.size() << " registers were leaked by script '" << m_module->getName().str() << "', function '" << func->getName().str()
             << "' (if you see this as a regular user, this is a bug and should be reported)" << std::endl;
     }
 
-    if (m_warp)
-        m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType));
-    else
-        m_builder.CreateBr(coro.freeMemRet);
-
     verifyFunction(func);
 
     // Create resume function
@@ -691,12 +668,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
     if (m_warp)
         m_builder.CreateRet(m_builder.getInt1(true));
-    else {
-        llvm::Value *coroHandle = func->getArg(0);
-        m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_resume), { coroHandle });
-        llvm::Value *done = m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_done), { coroHandle });
-        m_builder.CreateRet(done);
-    }
+    else
+        m_builder.CreateRet(coro->createResume(func->getArg(0)));
 
     verifyFunction(func);
 
@@ -1010,63 +983,6 @@ void LLVMCodeBuilder::createVariableMap()
     }
 }
 
-LLVMCoroutine LLVMCodeBuilder::initCoroutine(llvm::Function *func)
-{
-    // Set presplitcoroutine attribute
-    func->setPresplitCoroutine();
-
-    // Coroutine intrinsics
-    llvm::Function *coroId = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_id);
-    llvm::Function *coroSize = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_size, m_builder.getInt64Ty());
-    llvm::Function *coroBegin = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_begin);
-    llvm::Function *coroEnd = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_end);
-    llvm::Function *coroFree = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_free);
-
-    // Init coroutine
-    LLVMCoroutine coro;
-    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    llvm::Constant *nullPointer = llvm::ConstantPointerNull::get(pointerType);
-    llvm::Value *coroIdRet = m_builder.CreateCall(coroId, { m_builder.getInt32(8), nullPointer, nullPointer, nullPointer });
-
-    // Allocate memory
-    llvm::Value *coroSizeRet = m_builder.CreateCall(coroSize, std::nullopt, "size");
-    llvm::Function *mallocFunc = llvm::Function::Create(llvm::FunctionType::get(pointerType, { m_builder.getInt64Ty() }, false), llvm::Function::ExternalLinkage, "malloc", m_module.get());
-    llvm::Value *alloc = m_builder.CreateCall(mallocFunc, coroSizeRet, "mem");
-
-    // Begin
-    coro.handle = m_builder.CreateCall(coroBegin, { coroIdRet, alloc });
-    coro.didSuspend = m_builder.CreateAlloca(m_builder.getInt1Ty(), nullptr, "didSuspend");
-    m_builder.CreateStore(m_builder.getInt1(false), coro.didSuspend);
-    llvm::BasicBlock *entry = m_builder.GetInsertBlock();
-
-    // Create suspend branch
-    coro.suspend = llvm::BasicBlock::Create(m_ctx, "suspend", func);
-    m_builder.SetInsertPoint(coro.suspend);
-    m_builder.CreateCall(coroEnd, { coro.handle, m_builder.getInt1(false), llvm::ConstantTokenNone::get(m_ctx) });
-    m_builder.CreateRet(coro.handle);
-
-    // Create free branches
-    coro.freeMemRet = llvm::BasicBlock::Create(m_ctx, "freeMemRet", func);
-    m_builder.SetInsertPoint(coro.freeMemRet);
-    m_builder.CreateFree(alloc);
-    m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType));
-
-    llvm::BasicBlock *freeBranch = llvm::BasicBlock::Create(m_ctx, "free", func);
-    m_builder.SetInsertPoint(freeBranch);
-    m_builder.CreateFree(alloc);
-    m_builder.CreateBr(coro.suspend);
-
-    // Create cleanup branch
-    coro.cleanup = llvm::BasicBlock::Create(m_ctx, "cleanup", func);
-    m_builder.SetInsertPoint(coro.cleanup);
-    llvm::Value *mem = m_builder.CreateCall(coroFree, { coroIdRet, coro.handle });
-    llvm::Value *needFree = m_builder.CreateIsNotNull(mem);
-    m_builder.CreateCondBr(needFree, freeBranch, coro.suspend);
-
-    m_builder.SetInsertPoint(entry);
-    return coro;
-}
-
 void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
 {
     if (llvm::verifyFunction(*func, &llvm::errs())) {
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index db6a4243..2a89753b 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -84,7 +84,6 @@ class LLVMCodeBuilder : public ICodeBuilder
         void initTypes();
         void createVariableMap();
 
-        LLVMCoroutine initCoroutine(llvm::Function *func);
         void verifyFunction(llvm::Function *func);
         void optimize();
 
diff --git a/src/dev/engine/internal/llvm/llvmcoroutine.cpp b/src/dev/engine/internal/llvm/llvmcoroutine.cpp
new file mode 100644
index 00000000..6a4ad6e1
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmcoroutine.cpp
@@ -0,0 +1,130 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "llvmcoroutine.h"
+
+using namespace libscratchcpp;
+
+LLVMCoroutine::LLVMCoroutine(llvm::Module *module, llvm::IRBuilder<> *builder, llvm::Function *func) :
+    m_module(module),
+    m_builder(builder),
+    m_function(func)
+{
+    llvm::LLVMContext &ctx = builder->getContext();
+
+    // Set presplitcoroutine attribute
+    func->setPresplitCoroutine();
+
+    // Coroutine intrinsics
+    llvm::Function *coroId = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_id);
+    llvm::Function *coroSize = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_size, builder->getInt64Ty());
+    llvm::Function *coroBegin = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_begin);
+    llvm::Function *coroEnd = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_end);
+    llvm::Function *coroFree = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_free);
+
+    // Init coroutine
+    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(ctx), 0);
+    llvm::Constant *nullPointer = llvm::ConstantPointerNull::get(pointerType);
+    llvm::Value *coroIdRet = builder->CreateCall(coroId, { builder->getInt32(8), nullPointer, nullPointer, nullPointer });
+
+    // Allocate memory
+    llvm::Value *coroSizeRet = builder->CreateCall(coroSize, std::nullopt, "size");
+    llvm::Function *mallocFunc = llvm::Function::Create(llvm::FunctionType::get(pointerType, { builder->getInt64Ty() }, false), llvm::Function::ExternalLinkage, "malloc", module);
+    llvm::Value *alloc = builder->CreateCall(mallocFunc, coroSizeRet, "mem");
+
+    // Begin
+    m_handle = builder->CreateCall(coroBegin, { coroIdRet, alloc });
+    m_didSuspendVar = builder->CreateAlloca(builder->getInt1Ty(), nullptr, "didSuspend");
+    builder->CreateStore(builder->getInt1(false), m_didSuspendVar);
+    llvm::BasicBlock *entry = builder->GetInsertBlock();
+
+    // Create suspend branch
+    m_suspendBlock = llvm::BasicBlock::Create(ctx, "suspend", func);
+    builder->SetInsertPoint(m_suspendBlock);
+    builder->CreateCall(coroEnd, { m_handle, builder->getInt1(false), llvm::ConstantTokenNone::get(ctx) });
+    builder->CreateRet(m_handle);
+
+    // Create free branches
+    m_freeMemRetBlock = llvm::BasicBlock::Create(ctx, "freeMemRet", func);
+    builder->SetInsertPoint(m_freeMemRetBlock);
+    builder->CreateFree(alloc);
+    builder->CreateRet(llvm::ConstantPointerNull::get(pointerType));
+
+    llvm::BasicBlock *freeBranch = llvm::BasicBlock::Create(ctx, "free", func);
+    builder->SetInsertPoint(freeBranch);
+    builder->CreateFree(alloc);
+    builder->CreateBr(m_suspendBlock);
+
+    // Create cleanup branch
+    m_cleanupBlock = llvm::BasicBlock::Create(ctx, "cleanup", func);
+    builder->SetInsertPoint(m_cleanupBlock);
+    llvm::Value *mem = builder->CreateCall(coroFree, { coroIdRet, m_handle });
+    llvm::Value *needFree = builder->CreateIsNotNull(mem);
+    builder->CreateCondBr(needFree, freeBranch, m_suspendBlock);
+
+    builder->SetInsertPoint(entry);
+}
+
+llvm::Value *LLVMCoroutine::handle() const
+{
+    return m_handle;
+}
+
+llvm::BasicBlock *LLVMCoroutine::suspendBlock() const
+{
+    return m_suspendBlock;
+}
+
+llvm::BasicBlock *LLVMCoroutine::cleanupBlock() const
+{
+    return m_cleanupBlock;
+}
+
+llvm::BasicBlock *LLVMCoroutine::freeMemRetBlock() const
+{
+    return m_freeMemRetBlock;
+}
+
+llvm::Value *LLVMCoroutine::didSuspendVar() const
+{
+    return m_didSuspendVar;
+}
+
+void LLVMCoroutine::createSuspend()
+{
+    llvm::LLVMContext &ctx = m_builder->getContext();
+
+    m_builder->CreateStore(m_builder->getInt1(true), m_didSuspendVar);
+    llvm::BasicBlock *resumeBranch = llvm::BasicBlock::Create(ctx, "", m_function);
+    llvm::Value *noneToken = llvm::ConstantTokenNone::get(ctx);
+    llvm::Value *suspendResult = m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_suspend), { noneToken, m_builder->getInt1(false) });
+    llvm::SwitchInst *sw = m_builder->CreateSwitch(suspendResult, m_suspendBlock, 2);
+    sw->addCase(m_builder->getInt8(0), resumeBranch);
+    sw->addCase(m_builder->getInt8(1), m_cleanupBlock);
+    m_builder->SetInsertPoint(resumeBranch);
+}
+
+llvm::Value *LLVMCoroutine::createResume(llvm::Value *coroHandle)
+{
+    m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_resume), { coroHandle });
+    return m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_done), { coroHandle });
+}
+
+void LLVMCoroutine::end()
+{
+    llvm::LLVMContext &ctx = m_builder->getContext();
+
+    // Add final suspend point
+    llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(ctx, "end", m_function);
+    llvm::BasicBlock *finalSuspendBranch = llvm::BasicBlock::Create(ctx, "finalSuspend", m_function);
+    m_builder->CreateCondBr(m_builder->CreateLoad(m_builder->getInt1Ty(), m_didSuspendVar), finalSuspendBranch, endBranch);
+
+    m_builder->SetInsertPoint(finalSuspendBranch);
+    llvm::Value *suspendResult = m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_suspend), { llvm::ConstantTokenNone::get(ctx), m_builder->getInt1(true) });
+    llvm::SwitchInst *sw = m_builder->CreateSwitch(suspendResult, m_suspendBlock, 2);
+    sw->addCase(m_builder->getInt8(0), endBranch); // unreachable
+    sw->addCase(m_builder->getInt8(1), m_cleanupBlock);
+
+    // Jump to "free and return" branch
+    m_builder->SetInsertPoint(endBranch);
+    m_builder->CreateBr(m_freeMemRetBlock);
+}
diff --git a/src/dev/engine/internal/llvm/llvmcoroutine.h b/src/dev/engine/internal/llvm/llvmcoroutine.h
index c3efb0dc..2c715896 100644
--- a/src/dev/engine/internal/llvm/llvmcoroutine.h
+++ b/src/dev/engine/internal/llvm/llvmcoroutine.h
@@ -2,24 +2,38 @@
 
 #pragma once
 
-namespace llvm
-{
-
-class Value;
-class BasicBlock;
-
-} // namespace llvm
+#include <llvm/IR/IRBuilder.h>
 
 namespace libscratchcpp
 {
 
-struct LLVMCoroutine
+class LLVMCoroutine
 {
-        llvm::Value *handle = nullptr;
-        llvm::BasicBlock *suspend = nullptr;
-        llvm::BasicBlock *cleanup = nullptr;
-        llvm::BasicBlock *freeMemRet = nullptr;
-        llvm::Value *didSuspend = nullptr;
+    public:
+        LLVMCoroutine(llvm::Module *module, llvm::IRBuilder<> *builder, llvm::Function *func);
+        LLVMCoroutine(const LLVMCoroutine &) = delete;
+
+        llvm::Value *handle() const;
+
+        llvm::BasicBlock *suspendBlock() const;
+        llvm::BasicBlock *cleanupBlock() const;
+        llvm::BasicBlock *freeMemRetBlock() const;
+
+        llvm::Value *didSuspendVar() const;
+
+        void createSuspend();
+        llvm::Value *createResume(llvm::Value *coroHandle);
+        void end();
+
+    private:
+        llvm::Module *m_module = nullptr;
+        llvm::IRBuilder<> *m_builder = nullptr;
+        llvm::Function *m_function = nullptr;
+        llvm::Value *m_handle = nullptr;
+        llvm::BasicBlock *m_suspendBlock = nullptr;
+        llvm::BasicBlock *m_cleanupBlock = nullptr;
+        llvm::BasicBlock *m_freeMemRetBlock = nullptr;
+        llvm::Value *m_didSuspendVar = nullptr;
 };
 
 } // namespace libscratchcpp

From d8dc2bb3342d0a54523a9bdf723025417cf02cd4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Nov 2024 22:18:32 +0100
Subject: [PATCH 034/259] LLVMCodeBuilder: Set fast math flags

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index f0a42520..0ad66c9f 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -48,6 +48,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             m_warp = true;
     }
 
+    // Set fast math flags
+    llvm::FastMathFlags fmf;
+    fmf.setFast(true);
+    fmf.setNoNaNs(false);
+    fmf.setNoSignedZeros(false);
+    m_builder.setFastMathFlags(fmf);
+
     // Create function
     // void *f(Target *, ValueData **)
     llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);

From c8d69237d71b4653152c55699686309255c1907b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 11 Nov 2024 12:30:30 +0100
Subject: [PATCH 035/259] LLVMCodeBuilder: Store return type in return register

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 7 +++----
 src/dev/engine/internal/llvm/llvminstruction.h   | 1 -
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 0ad66c9f..07008993 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -107,12 +107,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     args.push_back(castValue(arg.second, arg.first));
                 }
 
-                llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(getType(step.functionReturnType), types, false)), args);
+                llvm::Type *retType = getType(step.functionReturnReg ? step.functionReturnReg->type : Compiler::StaticType::Void);
+                llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(retType, types, false)), args);
 
                 if (step.functionReturnReg) {
                     step.functionReturnReg->value = ret;
 
-                    if (step.functionReturnType == Compiler::StaticType::String)
+                    if (step.functionReturnReg->type == Compiler::StaticType::String)
                         m_heap.push_back(step.functionReturnReg->value);
                 }
 
@@ -705,8 +706,6 @@ void LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler:
 
     m_tmpRegs.erase(m_tmpRegs.end() - argTypes.size(), m_tmpRegs.end());
 
-    ins.functionReturnType = returnType;
-
     if (returnType != Compiler::StaticType::Void) {
         auto reg = std::make_shared<LLVMRegister>(returnType);
         reg->isRawValue = true;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 81fed9cd..41186cad 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -61,7 +61,6 @@ struct LLVMInstruction
         Type type;
         std::string functionName;
         std::vector<std::pair<Compiler::StaticType, LLVMRegisterPtr>> args; // target type, register
-        Compiler::StaticType functionReturnType = Compiler::StaticType::Void;
         LLVMRegisterPtr functionReturnReg;
         Variable *workVariable = nullptr; // for variables
 };

From 48cf8d30b3cb2e573e70599acc6a692ce980a9df Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 12 Nov 2024 11:26:14 +0100
Subject: [PATCH 036/259] LLVMCodeBuilder: Implement variable script type
 prediction

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 169 +++++++++--
 .../engine/internal/llvm/llvmcodebuilder.h    |   7 +-
 .../engine/internal/llvm/llvmvariableptr.h    |   3 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 271 ++++++++++++++++++
 4 files changed, 422 insertions(+), 28 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 07008993..5a304a71 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -80,16 +80,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     for (auto &[var, varPtr] : m_variablePtrs) {
         llvm::Value *ptr = getVariablePtr(targetVariables, var);
 
-        // Access variable directly (slow?)
-        // varPtr.ptr = ptr;
+        // Direct access
+        varPtr.heapPtr = ptr;
 
-        // All variables are currently copied to the stack and synced later (seems to be faster)
+        // All variables are currently created on the stack and synced later (seems to be faster)
         // NOTE: Strings are NOT copied, only the pointer and string size are copied
-        varPtr.ptr = m_builder.CreateAlloca(m_valueDataType);
-        varPtr.onStack = true;
-        createValueCopy(ptr, varPtr.ptr);
+        varPtr.stackPtr = m_builder.CreateAlloca(m_valueDataType);
+        varPtr.onStack = false; // use heap before the first assignment
     }
 
+    m_scopeVariables.clear();
+    m_scopeVariables.push_back({});
+
     // Execute recorded steps
     for (const LLVMInstruction &step : m_instructions) {
         switch (step.type) {
@@ -430,16 +432,41 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 assert(m_variablePtrs.find(step.workVariable) != m_variablePtrs.cend());
                 const auto &arg = step.args[0];
+                Compiler::StaticType type = optimizeRegisterType(arg.second);
                 LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
                 varPtr.changed = true;
-                createValueStore(arg.second, varPtr.ptr);
+
+                // Initialize stack variable on first assignment
+                if (!varPtr.onStack) {
+                    varPtr.onStack = true;
+                    varPtr.type = type; // don't care about unknown type on first assignment
+
+                    ValueType mappedType;
+
+                    if (type == Compiler::StaticType::String || type == Compiler::StaticType::Unknown) {
+                        // Value functions are used for these types, so don't break them
+                        mappedType = ValueType::Number;
+                    } else {
+                        auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [type](const std::pair<ValueType, Compiler::StaticType> &pair) { return pair.second == type; });
+                        assert(it != TYPE_MAP.cend());
+                        mappedType = it->first;
+                    }
+
+                    llvm::Value *typeField = m_builder.CreateStructGEP(m_valueDataType, varPtr.stackPtr, 1);
+                    m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typeField);
+                }
+
+                createValueStore(arg.second, varPtr.stackPtr, type, varPtr.type);
+                varPtr.type = type;
+                m_scopeVariables.back()[&varPtr] = varPtr.type;
                 break;
             }
 
             case LLVMInstruction::Type::ReadVariable: {
                 assert(step.args.size() == 0);
                 const LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
-                step.functionReturnReg->value = varPtr.ptr;
+                step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr;
+                step.functionReturnReg->type = varPtr.type;
                 break;
             }
 
@@ -448,6 +475,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     freeHeap();
                     syncVariables(targetVariables);
                     coro->createSuspend();
+                    reloadVariables(targetVariables);
                 }
 
                 break;
@@ -468,6 +496,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.SetInsertPoint(statement.body);
 
                 ifStatements.push_back(statement);
+                pushScopeLevel();
                 break;
             }
 
@@ -475,6 +504,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(!ifStatements.empty());
                 LLVMIfStatement &statement = ifStatements.back();
 
+                // Restore types from parent scope
+                std::unordered_map<LLVMVariablePtr *, Compiler::StaticType> parentScopeVariables = m_scopeVariables[m_scopeVariables.size() - 2]; // no reference!
+                popScopeLevel();
+
+                for (auto &[ptr, type] : parentScopeVariables)
+                    ptr->type = type;
+
+                pushScopeLevel();
+
                 // Jump to the branch after the if statement
                 assert(!statement.afterIf);
                 statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func);
@@ -516,6 +554,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.SetInsertPoint(statement.afterIf);
 
                 ifStatements.pop_back();
+                popScopeLevel();
                 break;
             }
 
@@ -569,6 +608,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.SetInsertPoint(body);
 
                 loops.push_back(loop);
+                pushScopeLevel();
                 break;
             }
 
@@ -590,6 +630,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Switch to body branch
                 m_builder.SetInsertPoint(body);
+                pushScopeLevel();
                 break;
             }
 
@@ -611,6 +652,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Switch to body branch
                 m_builder.SetInsertPoint(body);
+                pushScopeLevel();
                 break;
             }
 
@@ -644,6 +686,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.SetInsertPoint(loop.afterLoop);
 
                 loops.pop_back();
+                popScopeLevel();
                 break;
             }
         }
@@ -728,7 +771,6 @@ void LLVMCodeBuilder::addConstValue(const Value &value)
 
 void LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
-    // TODO: Implement type prediction
     LLVMInstruction ins(LLVMInstruction::Type::ReadVariable);
     ins.workVariable = variable;
     m_variablePtrs[variable] = LLVMVariablePtr();
@@ -989,6 +1031,23 @@ void LLVMCodeBuilder::createVariableMap()
     }
 }
 
+void LLVMCodeBuilder::pushScopeLevel()
+{
+    m_scopeVariables.push_back({});
+}
+
+void LLVMCodeBuilder::popScopeLevel()
+{
+    for (size_t i = 0; i < m_scopeVariables.size() - 1; i++) {
+        for (auto &[ptr, type] : m_scopeVariables[i]) {
+            if (ptr->type != type)
+                ptr->type = Compiler::StaticType::Unknown;
+        }
+    }
+
+    m_scopeVariables.pop_back();
+}
+
 void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
 {
     if (llvm::verifyFunction(*func, &llvm::errs())) {
@@ -1198,6 +1257,17 @@ llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::St
     }
 }
 
+Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegisterPtr reg)
+{
+    Compiler::StaticType ret = reg->type;
+
+    // Optimize string constants that represent numbers
+    if (reg->isConstValue && reg->type == Compiler::StaticType::String && reg->constValue.isValidNumber())
+        ret = Compiler::StaticType::Number;
+
+    return ret;
+}
+
 llvm::Type *LLVMCodeBuilder::getType(Compiler::StaticType type)
 {
     switch (type) {
@@ -1247,14 +1317,25 @@ llvm::Value *LLVMCodeBuilder::getVariablePtr(llvm::Value *targetVariables, Varia
 
 void LLVMCodeBuilder::syncVariables(llvm::Value *targetVariables)
 {
+    // Copy stack variables to the actual variables
     for (auto &[var, varPtr] : m_variablePtrs) {
         if (varPtr.onStack && varPtr.changed)
-            createValueCopy(varPtr.ptr, getVariablePtr(targetVariables, var));
+            createValueCopy(varPtr.stackPtr, getVariablePtr(targetVariables, var));
 
         varPtr.changed = false;
     }
 }
 
+void LLVMCodeBuilder::reloadVariables(llvm::Value *targetVariables)
+{
+    // Reset variables to use heap
+    for (auto &[var, varPtr] : m_variablePtrs) {
+        varPtr.onStack = false;
+        varPtr.changed = false;
+        varPtr.type = Compiler::StaticType::Unknown;
+    }
+}
+
 LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
 {
     LLVMInstruction ins(type);
@@ -1279,33 +1360,69 @@ LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler:
     return m_instructions.back();
 }
 
-void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr)
+void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType)
 {
-    // TODO: Implement type prediction
-    Compiler::StaticType type = reg->type;
     llvm::Value *converted = nullptr;
 
-    // Optimize string constants that represent numbers
-    if (reg->isConstValue && reg->type == Compiler::StaticType::String && reg->constValue.isValidNumber())
-        type = Compiler::StaticType::Number;
+    if (sourceType != Compiler::StaticType::Unknown)
+        converted = castValue(reg, sourceType);
 
-    switch (type) {
+    auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair<ValueType, Compiler::StaticType> &pair) { return pair.second == sourceType; });
+    const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored
+
+    switch (sourceType) {
         case Compiler::StaticType::Number:
-            converted = castValue(reg, type);
-            m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted });
-            /*{
-                llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
-                m_builder.CreateStore(converted, ptr);
-            }*/
+            switch (targetType) {
+                case Compiler::StaticType::Number: {
+                    // Write number to number directly
+                    llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    m_builder.CreateStore(converted, ptr);
+                    break;
+                }
+
+                case Compiler::StaticType::Bool: {
+                    // Write number to bool value directly and change type
+                    llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    m_builder.CreateStore(converted, ptr);
+                    m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typePtr);
+                    break;
+                }
+
+                default:
+                    m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted });
+                    break;
+            }
+
             break;
 
         case Compiler::StaticType::Bool:
-            converted = castValue(reg, type);
-            m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted });
+            switch (targetType) {
+                case Compiler::StaticType::Number: {
+                    // Write bool to number value directly and change type
+                    llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    m_builder.CreateStore(converted, ptr);
+                    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    m_builder.CreateStore(converted, ptr);
+                    m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typePtr);
+                    break;
+                }
+
+                case Compiler::StaticType::Bool: {
+                    // Write bool to bool directly
+                    llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    m_builder.CreateStore(converted, ptr);
+                    break;
+                }
+
+                default:
+                    m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted });
+                    break;
+            }
+
             break;
 
         case Compiler::StaticType::String:
-            converted = castValue(reg, type);
             m_builder.CreateCall(resolve_value_assign_cstring(), { targetPtr, converted });
             break;
 
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 2a89753b..6117b02f 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -83,6 +83,8 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void initTypes();
         void createVariableMap();
+        void pushScopeLevel();
+        void popScopeLevel();
 
         void verifyFunction(llvm::Function *func);
         void optimize();
@@ -91,16 +93,18 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *castValue(LLVMRegisterPtr reg, Compiler::StaticType targetType);
         llvm::Value *castRawValue(LLVMRegisterPtr reg, Compiler::StaticType targetType);
         llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType);
+        Compiler::StaticType optimizeRegisterType(LLVMRegisterPtr reg);
         llvm::Type *getType(Compiler::StaticType type);
         llvm::Value *isNaN(llvm::Value *num);
         llvm::Value *removeNaN(llvm::Value *num);
 
         llvm::Value *getVariablePtr(llvm::Value *targetVariables, Variable *variable);
         void syncVariables(llvm::Value *targetVariables);
+        void reloadVariables(llvm::Value *targetVariables);
 
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
 
-        void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr);
+        void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
         llvm::Value *createValue(LLVMRegisterPtr reg);
@@ -130,6 +134,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         Target *m_target = nullptr;
         std::unordered_map<Variable *, size_t> m_targetVariableMap;
         std::unordered_map<Variable *, LLVMVariablePtr> m_variablePtrs;
+        std::vector<std::unordered_map<LLVMVariablePtr *, Compiler::StaticType>> m_scopeVariables;
 
         std::string m_id;
         llvm::LLVMContext m_ctx;
diff --git a/src/dev/engine/internal/llvm/llvmvariableptr.h b/src/dev/engine/internal/llvm/llvmvariableptr.h
index bfc65e3f..c2525dfd 100644
--- a/src/dev/engine/internal/llvm/llvmvariableptr.h
+++ b/src/dev/engine/internal/llvm/llvmvariableptr.h
@@ -16,7 +16,8 @@ namespace libscratchcpp
 
 struct LLVMVariablePtr
 {
-        llvm::Value *ptr = nullptr;
+        llvm::Value *stackPtr = nullptr;
+        llvm::Value *heapPtr = nullptr;
         Compiler::StaticType type = Compiler::StaticType::Unknown;
         bool onStack = false;
         bool changed = false;
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index b0597e2a..58e467d4 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1973,6 +1973,53 @@ TEST_F(LLVMCodeBuilderTest, Yield)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "", 87);
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "", "test");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, false);
+
+    m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get());
+
+    m_builder->addConstValue(true);
+    m_builder->createVariableWrite(localVar.get());
+
+    m_builder->yield();
+
+    m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    std::string expected =
+        "hello world\n"
+        "-4.8\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    globalVar->setValue("hello world");
+    localVar->setValue(-4.8);
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, IfStatement)
 {
     createBuilder(true);
@@ -2144,6 +2191,100 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, IfStatementVariables)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "", "test");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "", 87);
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get());
+
+    m_builder->addConstValue(true);
+    m_builder->createVariableWrite(localVar.get());
+
+    m_builder->addConstValue(true);
+    m_builder->beginIfStatement();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get());
+    }
+    m_builder->endIf();
+
+    m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get());
+
+    m_builder->addConstValue(false);
+    m_builder->beginIfStatement();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get());
+
+        m_builder->addConstValue(0);
+        m_builder->createVariableWrite(localVar.get());
+    }
+    m_builder->endIf();
+
+    m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(true);
+    m_builder->beginIfStatement();
+    {
+        m_builder->addConstValue(true);
+        m_builder->beginIfStatement();
+        {
+            m_builder->addConstValue(true);
+            m_builder->createVariableWrite(globalVar.get());
+        }
+        m_builder->endIf();
+
+        m_builder->addConstValue(false);
+        m_builder->beginIfStatement();
+        {
+            m_builder->addConstValue(-8.2);
+            m_builder->createVariableWrite(localVar.get());
+        }
+        m_builder->endIf();
+    }
+    m_builder->endIf();
+
+    m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    std::string expected =
+        "hello world\n"
+        "12.5\n"
+        "true\n"
+        "true\n"
+        "true\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, RepeatLoop)
 {
     createBuilder(true);
@@ -2486,3 +2627,133 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
         ASSERT_FALSE(code->isFinished(ctx.get()));
     }
 }
+
+TEST_F(LLVMCodeBuilderTest, LoopVariables)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "", "test");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "", 87);
+    auto counter1 = std::make_shared<Variable>("", "");
+    auto counter2 = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+    sprite.addVariable(counter1);
+    sprite.addVariable(counter2);
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get());
+
+    m_builder->addConstValue(true);
+    m_builder->createVariableWrite(localVar.get());
+
+    m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get());
+    }
+    m_builder->endLoop();
+
+    m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get());
+
+    m_builder->addConstValue(0);
+    m_builder->beginRepeatLoop();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get());
+
+        m_builder->addConstValue(0);
+        m_builder->createVariableWrite(localVar.get());
+    }
+    m_builder->endLoop();
+
+    m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(0);
+    m_builder->createVariableWrite(counter1.get());
+
+    m_builder->beginLoopCondition();
+    m_builder->addVariableValue(counter1.get());
+    m_builder->addConstValue(5);
+    m_builder->createCmpLT();
+    m_builder->beginWhileLoop();
+    {
+        m_builder->addConstValue(0);
+        m_builder->createVariableWrite(counter2.get());
+
+        m_builder->beginLoopCondition();
+        m_builder->addVariableValue(counter2.get());
+        m_builder->addConstValue(3);
+        m_builder->createCmpEQ();
+        m_builder->beginRepeatUntilLoop();
+        {
+            m_builder->addConstValue(true);
+            m_builder->createVariableWrite(globalVar.get());
+
+            m_builder->addVariableValue(counter2.get());
+            m_builder->addConstValue(1);
+            m_builder->createAdd();
+            m_builder->createVariableWrite(counter2.get());
+        }
+        m_builder->endLoop();
+
+        m_builder->beginLoopCondition();
+        m_builder->addConstValue(false);
+        m_builder->beginWhileLoop();
+        {
+            m_builder->addConstValue(-8.2);
+            m_builder->createVariableWrite(localVar.get());
+        }
+        m_builder->endLoop();
+
+        m_builder->addVariableValue(counter1.get());
+        m_builder->addConstValue(1);
+        m_builder->createAdd();
+        m_builder->createVariableWrite(counter1.get());
+    }
+    m_builder->endLoop();
+
+    m_builder->beginLoopCondition();
+    m_builder->addConstValue(true);
+    m_builder->beginRepeatUntilLoop();
+    {
+        m_builder->addConstValue(-8.2);
+        m_builder->createVariableWrite(localVar.get());
+    }
+    m_builder->endLoop();
+
+    m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    std::string expected =
+        "hello world\n"
+        "12.5\n"
+        "true\n"
+        "true\n"
+        "true\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}

From d1cfb5d8f92178bcc1395cca672392fd10ab2034 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 12 Nov 2024 20:00:53 +0100
Subject: [PATCH 037/259] Target: Add listData() method

---
 include/scratchcpp/target.h          |  2 ++
 src/scratch/target.cpp               | 26 ++++++++++++++++++++++++++
 src/scratch/target_p.h               |  2 ++
 test/scratch_classes/target_test.cpp |  8 ++++++++
 4 files changed, 38 insertions(+)

diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h
index b9d89eb5..20d1a389 100644
--- a/include/scratchcpp/target.h
+++ b/include/scratchcpp/target.h
@@ -53,6 +53,8 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable
         int findList(const std::string &listName) const;
         int findListById(const std::string &id) const;
 
+        List **listData();
+
         const std::vector<std::shared_ptr<Block>> &blocks() const;
         int addBlock(std::shared_ptr<Block> block);
         std::shared_ptr<Block> blockAt(int index) const;
diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index c25b2f38..aa555d74 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -150,6 +150,7 @@ int Target::addList(std::shared_ptr<List> list)
         return it - impl->lists.begin();
 
     impl->lists.push_back(list);
+    impl->listDataDirty = true;
     list->setTarget(this);
 
     return impl->lists.size() - 1;
@@ -186,6 +187,31 @@ int Target::findListById(const std::string &id) const
         return it - impl->lists.begin();
 }
 
+/*! Returns an array of list pointers (for optimized list access). */
+List **Target::listData()
+{
+    if (impl->listDataDirty) {
+        const size_t len = impl->lists.size();
+
+        if (len == 0) {
+            impl->listDataDirty = false;
+            return nullptr;
+        }
+
+        if (impl->listData)
+            impl->listData = (List **)realloc(impl->listData, len * sizeof(List *));
+        else
+            impl->listData = (List **)malloc(len * sizeof(List *));
+
+        for (size_t i = 0; i < len; i++)
+            impl->listData[i] = impl->lists[i].get();
+
+        impl->listDataDirty = false;
+    }
+
+    return impl->listData;
+}
+
 /*! Returns the list of blocks. */
 const std::vector<std::shared_ptr<Block>> &Target::blocks() const
 {
diff --git a/src/scratch/target_p.h b/src/scratch/target_p.h
index fc18bfb8..1261330a 100644
--- a/src/scratch/target_p.h
+++ b/src/scratch/target_p.h
@@ -31,6 +31,8 @@ struct TargetPrivate
         bool variableDataDirty = true;
         ValueData **variableData = nullptr;
         std::vector<std::shared_ptr<List>> lists;
+        bool listDataDirty = true;
+        List **listData = nullptr;
         std::vector<std::shared_ptr<Block>> blocks;
         std::vector<std::shared_ptr<Comment>> comments;
         int costumeIndex = -1;
diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index f83f46fa..93ef3330 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -119,9 +119,17 @@ TEST(TargetTest, Lists)
 
     Target target;
     ASSERT_EQ(target.addList(l1), 0);
+    ASSERT_TRUE(target.listData());
+    ASSERT_EQ(target.listData()[0], l1.get());
     ASSERT_EQ(target.addList(l2), 1);
     ASSERT_EQ(target.addList(l3), 2);
+    ASSERT_EQ(target.listData()[0], l1.get());
+    ASSERT_EQ(target.listData()[1], l2.get());
+    ASSERT_EQ(target.listData()[2], l3.get());
     ASSERT_EQ(target.addList(l2), 1); // add existing list
+    ASSERT_EQ(target.listData()[0], l1.get());
+    ASSERT_EQ(target.listData()[1], l2.get());
+    ASSERT_EQ(target.listData()[2], l3.get());
 
     ASSERT_EQ(l1->target(), &target);
     ASSERT_EQ(l2->target(), &target);

From a22f8d308b89972638b2529abaf147a25e15227b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 12 Nov 2024 20:03:37 +0100
Subject: [PATCH 038/259] LLVMCodeBuilder: Initial list limplementation

---
 src/dev/engine/internal/llvm/CMakeLists.txt   |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 57 ++++++++++++++++++-
 .../engine/internal/llvm/llvmcodebuilder.h    |  7 +++
 .../internal/llvm/llvmexecutablecode.cpp      |  2 +-
 .../engine/internal/llvm/llvmexecutablecode.h |  3 +-
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 src/dev/engine/internal/llvm/llvmlistptr.h    | 23 ++++++++
 test/dev/llvm/llvmcodebuilder_test.cpp        |  2 +-
 test/dev/llvm/llvmexecutablecode_test.cpp     | 19 +++++--
 test/dev/llvm/testfunctions.cpp               |  4 +-
 test/dev/llvm/testfunctions.h                 |  3 +-
 test/dev/llvm/testmock.h                      |  4 +-
 12 files changed, 111 insertions(+), 15 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmlistptr.h

diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index a7833f5b..1a4ce89a 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -9,6 +9,7 @@ target_sources(scratchcpp
     llvmcoroutine.cpp
     llvmcoroutine.h
     llvmvariableptr.h
+    llvmlistptr.h
     llvmprocedure.h
     llvmtypes.cpp
     llvmtypes.h
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 5a304a71..46153824 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -8,6 +8,7 @@
 #include <scratchcpp/stage.h>
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
 
 #include "llvmcodebuilder.h"
 #include "llvmexecutablecode.h"
@@ -36,6 +37,7 @@ LLVMCodeBuilder::LLVMCodeBuilder(Target *target, const std::string &id, bool war
     m_regs.push_back({});
     initTypes();
     createVariableMap();
+    createListMap();
 }
 
 std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
@@ -56,12 +58,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_builder.setFastMathFlags(fmf);
 
     // Create function
-    // void *f(Target *, ValueData **)
+    // void *f(Target *, ValueData **, List **)
     llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType }, false);
+    llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType }, false);
     llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
     llvm::Value *targetPtr = func->getArg(0);
     llvm::Value *targetVariables = func->getArg(1);
+    llvm::Value *targetLists = func->getArg(2);
 
     llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
     m_builder.SetInsertPoint(entry);
@@ -92,6 +95,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_scopeVariables.clear();
     m_scopeVariables.push_back({});
 
+    // Create list pointers
+    for (auto &[list, listPtr] : m_listPtrs)
+        listPtr.ptr = getListPtr(targetLists, list);
+
     // Execute recorded steps
     for (const LLVMInstruction &step : m_instructions) {
         switch (step.type) {
@@ -1031,6 +1038,36 @@ void LLVMCodeBuilder::createVariableMap()
     }
 }
 
+void LLVMCodeBuilder::createListMap()
+{
+    if (!m_target)
+        return;
+
+    // Map list pointers to list array indices
+    const auto &lists = m_target->lists();
+    List **listData = m_target->listData();
+    const size_t len = lists.size();
+    m_targetListMap.clear();
+    m_targetListMap.reserve(len);
+
+    size_t i, j;
+
+    for (i = 0; i < len; i++) {
+        List *list = lists[i].get();
+
+        // Find this list
+        for (j = 0; j < len; j++) {
+            if (listData[j] == list)
+                break;
+        }
+
+        if (j < len)
+            m_targetListMap[list] = j;
+        else
+            assert(false);
+    }
+}
+
 void LLVMCodeBuilder::pushScopeLevel()
 {
     m_scopeVariables.push_back({});
@@ -1315,6 +1352,22 @@ llvm::Value *LLVMCodeBuilder::getVariablePtr(llvm::Value *targetVariables, Varia
     return m_builder.CreateIntToPtr(addr, m_valueDataType->getPointerTo());
 }
 
+llvm::Value *LLVMCodeBuilder::getListPtr(llvm::Value *targetLists, List *list)
+{
+    if (!m_target->isStage() && list->target() == m_target) {
+        // If this is a local sprite list, use the list array at runtime (for clones)
+        assert(m_targetListMap.find(list) != m_targetListMap.cend());
+        const size_t index = m_targetListMap[list];
+        auto pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+        llvm::Value *ptr = m_builder.CreateGEP(pointerType, targetLists, m_builder.getInt64(index));
+        return m_builder.CreateLoad(pointerType, ptr);
+    }
+
+    // Otherwise create a raw pointer at compile time
+    llvm::Value *addr = m_builder.getInt64((uintptr_t)list);
+    return m_builder.CreateIntToPtr(addr, m_valueDataType->getPointerTo());
+}
+
 void LLVMCodeBuilder::syncVariables(llvm::Value *targetVariables)
 {
     // Copy stack variables to the actual variables
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 6117b02f..4dc501f8 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -11,6 +11,7 @@
 #include "llvminstruction.h"
 #include "llvmcoroutine.h"
 #include "llvmvariableptr.h"
+#include "llvmlistptr.h"
 
 namespace libscratchcpp
 {
@@ -83,6 +84,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void initTypes();
         void createVariableMap();
+        void createListMap();
         void pushScopeLevel();
         void popScopeLevel();
 
@@ -99,6 +101,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *removeNaN(llvm::Value *num);
 
         llvm::Value *getVariablePtr(llvm::Value *targetVariables, Variable *variable);
+        llvm::Value *getListPtr(llvm::Value *targetLists, List *list);
         void syncVariables(llvm::Value *targetVariables);
         void reloadVariables(llvm::Value *targetVariables);
 
@@ -132,10 +135,14 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
+
         std::unordered_map<Variable *, size_t> m_targetVariableMap;
         std::unordered_map<Variable *, LLVMVariablePtr> m_variablePtrs;
         std::vector<std::unordered_map<LLVMVariablePtr *, Compiler::StaticType>> m_scopeVariables;
 
+        std::unordered_map<List *, size_t> m_targetListMap;
+        std::unordered_map<List *, LLVMListPtr> m_listPtrs;
+
         std::string m_id;
         llvm::LLVMContext m_ctx;
         std::unique_ptr<llvm::Module> m_module;
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
index 3357be56..eb8b8b03 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
@@ -55,7 +55,7 @@ void LLVMExecutableCode::run(ExecutionContext *context)
         ctx->setFinished(done);
     } else {
         Target *target = ctx->target();
-        void *handle = m_mainFunction(target, target->variableData());
+        void *handle = m_mainFunction(target, target->variableData(), target->listData());
 
         if (!handle)
             ctx->setFinished(true);
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h
index e066cd9c..26e412dd 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.h
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.h
@@ -11,6 +11,7 @@ namespace libscratchcpp
 {
 
 class Target;
+class List;
 class LLVMExecutionContext;
 
 class LLVMExecutableCode : public ExecutableCode
@@ -32,7 +33,7 @@ class LLVMExecutableCode : public ExecutableCode
     private:
         uint64_t lookupFunction(const std::string &name);
 
-        using MainFunctionType = void *(*)(Target *, ValueData **);
+        using MainFunctionType = void *(*)(Target *, ValueData **, List **);
         using ResumeFunctionType = bool (*)(void *);
 
         static LLVMExecutionContext *getContext(ExecutionContext *context);
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 41186cad..32267518 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -63,6 +63,7 @@ struct LLVMInstruction
         std::vector<std::pair<Compiler::StaticType, LLVMRegisterPtr>> args; // target type, register
         LLVMRegisterPtr functionReturnReg;
         Variable *workVariable = nullptr; // for variables
+        List *workList = nullptr;         // for lists
 };
 
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h
new file mode 100644
index 00000000..9dc93ec4
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmlistptr.h
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/dev/compiler.h>
+
+namespace llvm
+{
+
+class Value;
+
+}
+
+namespace libscratchcpp
+{
+
+struct LLVMListPtr
+{
+        llvm::Value *ptr = nullptr;
+        Compiler::StaticType type = Compiler::StaticType::Unknown;
+};
+
+} // namespace libscratchcpp
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 58e467d4..743cb969 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -20,7 +20,7 @@ class LLVMCodeBuilderTest : public testing::Test
     public:
         void SetUp() override
         {
-            test_function(nullptr, nullptr); // force dependency
+            test_function(nullptr, nullptr, nullptr, nullptr); // force dependency
         }
 
         void createBuilder(Target *target, bool warp) { m_builder = std::make_unique<LLVMCodeBuilder>(target, "test", warp); }
diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp
index 8af37328..84e86d15 100644
--- a/test/dev/llvm/llvmexecutablecode_test.cpp
+++ b/test/dev/llvm/llvmexecutablecode_test.cpp
@@ -1,5 +1,7 @@
 #include <scratchcpp/target.h>
 #include <scratchcpp/value_functions.h>
+#include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
 #include <dev/engine/internal/llvm/llvmexecutablecode.h>
 #include <dev/engine/internal/llvm/llvmexecutioncontext.h>
 #include <llvm/Support/TargetSelect.h>
@@ -18,7 +20,7 @@ class LLVMExecutableCodeTest : public testing::Test
         {
             m_module = std::make_unique<llvm::Module>("test", m_ctx);
             m_builder = std::make_unique<llvm::IRBuilder<>>(m_ctx);
-            test_function(nullptr, nullptr); // force dependency
+            test_function(nullptr, nullptr, nullptr, nullptr); // force dependency
 
             llvm::InitializeNativeTarget();
             llvm::InitializeNativeTargetAsmPrinter();
@@ -29,7 +31,7 @@ class LLVMExecutableCodeTest : public testing::Test
 
         llvm::Function *beginMainFunction()
         {
-            // void *f(Target *, ValueData **, ValueData **)
+            // void *f(Target *, ValueData **, List **)
             llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
             llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType }, false);
             llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
@@ -55,12 +57,12 @@ class LLVMExecutableCodeTest : public testing::Test
         void addTestFunction(llvm::Function *mainFunc)
         {
             auto ptrType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-            auto func = m_module->getOrInsertFunction("test_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType }, false));
+            auto func = m_module->getOrInsertFunction("test_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType, ptrType, ptrType }, false));
 
             llvm::Constant *mockInt = llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_ctx), (uintptr_t)&m_mock, false);
             llvm::Constant *mockPtr = llvm::ConstantExpr::getIntToPtr(mockInt, ptrType);
 
-            m_builder->CreateCall(func, { mockPtr, mainFunc->getArg(0) });
+            m_builder->CreateCall(func, { mockPtr, mainFunc->getArg(0), mainFunc->getArg(1), mainFunc->getArg(2) });
         }
 
         void addTestPrintFunction(llvm::Value *arg1, llvm::Value *arg2)
@@ -94,6 +96,9 @@ TEST_F(LLVMExecutableCodeTest, CreateExecutionContext)
 
 TEST_F(LLVMExecutableCodeTest, MainFunction)
 {
+    m_target.addVariable(std::make_shared<Variable>("", ""));
+    m_target.addList(std::make_shared<List>("", ""));
+
     auto f = beginMainFunction();
     addTestFunction(f);
     endFunction(nullPointer());
@@ -105,7 +110,7 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
     auto ctx = code.createExecutionContext(&m_target);
     ASSERT_FALSE(code.isFinished(ctx.get()));
 
-    EXPECT_CALL(m_mock, f(&m_target));
+    EXPECT_CALL(m_mock, f(&m_target, m_target.variableData(), m_target.listData()));
     code.run(ctx.get());
     ASSERT_TRUE(code.isFinished(ctx.get()));
 
@@ -124,11 +129,13 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
 
     // Test with another context
     Target anotherTarget;
+    anotherTarget.addVariable(std::make_shared<Variable>("", ""));
+    anotherTarget.addList(std::make_shared<List>("", ""));
     auto anotherCtx = code.createExecutionContext(&anotherTarget);
     ASSERT_FALSE(code.isFinished(anotherCtx.get()));
     ASSERT_TRUE(code.isFinished(ctx.get()));
 
-    EXPECT_CALL(m_mock, f(&anotherTarget));
+    EXPECT_CALL(m_mock, f(&anotherTarget, anotherTarget.variableData(), anotherTarget.listData()));
     code.run(anotherCtx.get());
     ASSERT_TRUE(code.isFinished(anotherCtx.get()));
     ASSERT_TRUE(code.isFinished(ctx.get()));
diff --git a/test/dev/llvm/testfunctions.cpp b/test/dev/llvm/testfunctions.cpp
index 86ecb8b6..8555ee24 100644
--- a/test/dev/llvm/testfunctions.cpp
+++ b/test/dev/llvm/testfunctions.cpp
@@ -11,10 +11,10 @@ static int counter = 0;
 
 extern "C"
 {
-    void test_function(TestMock *mock, Target *target)
+    void test_function(TestMock *mock, Target *target, ValueData **varData, List **listData)
     {
         if (mock)
-            mock->f(target);
+            mock->f(target, varData, listData);
     }
 
     void test_print_function(ValueData *arg1, ValueData *arg2)
diff --git a/test/dev/llvm/testfunctions.h b/test/dev/llvm/testfunctions.h
index 94f8a76a..c20c8efa 100644
--- a/test/dev/llvm/testfunctions.h
+++ b/test/dev/llvm/testfunctions.h
@@ -6,10 +6,11 @@ namespace libscratchcpp
 class TestMock;
 class Target;
 class ValueData;
+class List;
 
 extern "C"
 {
-    void test_function(TestMock *mock, Target *target);
+    void test_function(TestMock *mock, Target *target, ValueData **varData, List **listData);
     void test_print_function(ValueData *arg1, ValueData *arg2);
 
     void test_function_no_args(Target *target);
diff --git a/test/dev/llvm/testmock.h b/test/dev/llvm/testmock.h
index 9b26d1fa..176eeff2 100644
--- a/test/dev/llvm/testmock.h
+++ b/test/dev/llvm/testmock.h
@@ -6,11 +6,13 @@ namespace libscratchcpp
 {
 
 class Target;
+struct ValueData;
+class List;
 
 class TestMock
 {
     public:
-        MOCK_METHOD(void, f, (Target *));
+        MOCK_METHOD(void, f, (Target *, ValueData **, List **));
 };
 
 } // namespace libscratchcpp

From 8d0d24fccb10998cc6f60d36eb6ef1235b559fa6 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 12 Nov 2024 21:36:11 +0100
Subject: [PATCH 039/259] Add list_clear() function

---
 src/scratch/CMakeLists.txt                   |  2 ++
 src/scratch/list_functions.cpp               | 15 +++++++++++++++
 src/scratch/list_functions.h                 | 15 +++++++++++++++
 test/scratch_classes/CMakeLists.txt          |  1 +
 test/scratch_classes/list_functions_test.cpp | 16 ++++++++++++++++
 5 files changed, 49 insertions(+)
 create mode 100644 src/scratch/list_functions.cpp
 create mode 100644 src/scratch/list_functions.h
 create mode 100644 test/scratch_classes/list_functions_test.cpp

diff --git a/src/scratch/CMakeLists.txt b/src/scratch/CMakeLists.txt
index 926cb2b0..71d6c8c0 100644
--- a/src/scratch/CMakeLists.txt
+++ b/src/scratch/CMakeLists.txt
@@ -14,6 +14,8 @@ target_sources(scratchcpp
     list.cpp
     list_p.cpp
     list_p.h
+    list_functions.cpp
+    list_functions.h
     block.cpp
     block_p.cpp
     block_p.h
diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
new file mode 100644
index 00000000..a5ecb988
--- /dev/null
+++ b/src/scratch/list_functions.cpp
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/list.h>
+
+#include "list_functions.h"
+
+using namespace libscratchcpp;
+
+extern "C"
+{
+    void list_clear(List *list)
+    {
+        list->clear();
+    }
+}
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
new file mode 100644
index 00000000..89fc702d
--- /dev/null
+++ b/src/scratch/list_functions.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace libscratchcpp
+{
+
+class List;
+
+extern "C"
+{
+    void list_clear(List *list);
+}
+
+} // namespace libscratchcpp
diff --git a/test/scratch_classes/CMakeLists.txt b/test/scratch_classes/CMakeLists.txt
index 279727bb..511cf190 100644
--- a/test/scratch_classes/CMakeLists.txt
+++ b/test/scratch_classes/CMakeLists.txt
@@ -16,6 +16,7 @@ gtest_discover_tests(blockprototype_test)
 add_executable(
   list_test
   list_test.cpp
+  list_functions_test.cpp
 )
 
 target_link_libraries(
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
new file mode 100644
index 00000000..56a9e3e2
--- /dev/null
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -0,0 +1,16 @@
+#include <scratchcpp/list.h>
+#include <scratch/list_functions.h>
+
+#include <gtest/gtest.h>
+
+using namespace libscratchcpp;
+
+TEST(ListFunctionsTest, Clear)
+{
+    List list("", "");
+    list.append(1);
+    list.append(2);
+    list.append(3);
+
+    list_clear(&list);
+}

From 6ad13ee2a691ddc923743650ca3c04621e1b832d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 12 Nov 2024 21:49:32 +0100
Subject: [PATCH 040/259] LLVMCodeBuilder: Implement list clear

---
 src/dev/engine/internal/icodebuilder.h        |  2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 20 +++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  3 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 75 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  2 +
 6 files changed, 103 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 8ca7fbea..2e373a36 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -56,6 +56,8 @@ class ICodeBuilder
 
         virtual void createVariableWrite(Variable *variable) = 0;
 
+        virtual void createListClear(List *list) = 0;
+
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
         virtual void endIf() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 46153824..93606c14 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -477,6 +477,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::ClearList: {
+                assert(step.args.size() == 0);
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                m_builder.CreateCall(resolve_list_clear(), listPtr.ptr);
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -932,6 +939,13 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable)
     m_variablePtrs[variable] = LLVMVariablePtr();
 }
 
+void LLVMCodeBuilder::createListClear(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::ClearList, Compiler::StaticType::Void, Compiler::StaticType::Void, 0);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
@@ -1822,6 +1836,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower()
     return resolveFunction("value_lower", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_clear()
+{
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_clear", llvm::FunctionType::get(m_builder.getInt1Ty(), { listPtr }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 4dc501f8..6fefad72 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -62,6 +62,8 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void createVariableWrite(Variable *variable) override;
 
+        void createListClear(List *list) override;
+
         void beginIfStatement() override;
         void beginElseBranch() override;
         void endIf() override;
@@ -132,6 +134,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_value_equals();
         llvm::FunctionCallee resolve_value_greater();
         llvm::FunctionCallee resolve_value_lower();
+        llvm::FunctionCallee resolve_list_clear();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 32267518..300f3c11 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -42,6 +42,7 @@ struct LLVMInstruction
             Exp10,
             WriteVariable,
             ReadVariable,
+            ClearList,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 743cb969..61c82a13 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -3,6 +3,7 @@
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/stage.h>
 #include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
 #include <dev/engine/internal/llvm/llvmcodebuilder.h>
 #include <gmock/gmock.h>
 #include <targetmock.h>
@@ -1899,6 +1900,80 @@ TEST_F(LLVMCodeBuilderTest, ReadVariable)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, ClearList)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    std::unordered_map<List *, std::string> strings;
+
+    auto globalList1 = std::make_shared<List>("", "");
+    auto globalList2 = std::make_shared<List>("", "");
+    auto globalList3 = std::make_shared<List>("", "");
+    stage.addList(globalList1);
+    stage.addList(globalList2);
+    stage.addList(globalList3);
+
+    auto localList1 = std::make_shared<List>("", "");
+    auto localList2 = std::make_shared<List>("", "");
+    auto localList3 = std::make_shared<List>("", "");
+    sprite.addList(localList1);
+    sprite.addList(localList2);
+    sprite.addList(localList3);
+
+    globalList1->append(1);
+    globalList1->append(2);
+    globalList1->append(3);
+    strings[globalList1.get()] = globalList1->toString();
+
+    globalList2->append("Lorem");
+    globalList2->append("ipsum");
+    globalList2->append(-4.52);
+    strings[globalList2.get()] = globalList2->toString();
+
+    globalList3->append(true);
+    globalList3->append(false);
+    globalList3->append(true);
+    strings[globalList3.get()] = globalList3->toString();
+
+    localList1->append("dolor");
+    localList1->append("sit");
+    localList1->append("amet");
+    strings[localList1.get()] = localList1->toString();
+
+    localList2->append(10);
+    localList2->append(9.8);
+    localList2->append(true);
+    strings[localList2.get()] = localList2->toString();
+
+    localList3->append("test");
+    localList3->append(1.2);
+    localList3->append(false);
+    strings[localList3.get()] = localList3->toString();
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(globalList1.get());
+    m_builder->createListClear(globalList3.get());
+    m_builder->createListClear(localList1.get());
+    m_builder->createListClear(localList2.get());
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+
+    ASSERT_TRUE(globalList1->empty());
+    ASSERT_EQ(globalList2->toString(), strings[globalList2.get()]);
+    ASSERT_TRUE(globalList3->empty());
+
+    ASSERT_TRUE(localList1->empty());
+    ASSERT_TRUE(localList2->empty());
+    ASSERT_EQ(localList3->toString(), strings[localList3.get()]);
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 0028f76b..6589d1b0 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -46,6 +46,8 @@ class CodeBuilderMock : public ICodeBuilder
 
         MOCK_METHOD(void, createVariableWrite, (Variable *), (override));
 
+        MOCK_METHOD(void, createListClear, (List *), (override));
+
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));
         MOCK_METHOD(void, endIf, (), (override));

From 06f551e588b089a7be74a3bc62caad22a92e579d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 14 Nov 2024 20:55:20 +0100
Subject: [PATCH 041/259] Compiler: Add custom if statement methods

---
 include/scratchcpp/dev/compiler.h   |  4 +++
 src/dev/engine/compiler.cpp         | 39 +++++++++++++++++++++++++++--
 src/dev/engine/compiler_p.h         |  1 +
 test/dev/compiler/compiler_test.cpp | 22 ++++++++++++++++
 4 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index f5b2c7fb..fa6550c8 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -80,6 +80,10 @@ class LIBSCRATCHCPP_EXPORT Compiler
 
         void createVariableWrite(Variable *variable);
 
+        void beginIfStatement();
+        void beginElseBranch();
+        void endIf();
+
         void moveToIf(std::shared_ptr<Block> substack);
         void moveToIfElse(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2);
         void moveToRepeatLoop(std::shared_ptr<Block> substack);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 1dde27ab..ed225ff2 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -45,9 +45,15 @@ std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBl
     impl->block = startBlock;
 
     while (impl->block) {
-        if (impl->block->compileFunction())
+        if (impl->block->compileFunction()) {
+            assert(impl->customIfStatementCount == 0);
             impl->block->compile(this);
-        else {
+
+            if (impl->customIfStatementCount > 0) {
+                std::cerr << "error: if statement created by block '" << impl->block->opcode() << "' not terminated" << std::endl;
+                assert(false);
+            }
+        } else {
             std::cout << "warning: unsupported block: " << impl->block->opcode() << std::endl;
             impl->unsupportedBlocks.insert(impl->block->opcode());
         }
@@ -262,6 +268,35 @@ void Compiler::createVariableWrite(Variable *variable)
     impl->builder->createVariableWrite(variable);
 }
 
+/*!
+ * Starts a custom if statement.
+ * \note The if statement must be terminated using endIf() after compiling your block.
+ */
+void Compiler::beginIfStatement()
+{
+    impl->builder->beginIfStatement();
+    impl->customIfStatementCount++;
+}
+
+/*! Starts the else branch of custom if statement. */
+void Compiler::beginElseBranch()
+{
+    impl->builder->beginElseBranch();
+}
+
+/*! Ends custom if statement. */
+void Compiler::endIf()
+{
+    if (impl->customIfStatementCount == 0) {
+        std::cerr << "error: called Compiler::endIf() without an if statement";
+        assert(false);
+        return;
+    }
+
+    impl->builder->endIf();
+    impl->customIfStatementCount--;
+}
+
 /*! Jumps to the given if substack. */
 void Compiler::moveToIf(std::shared_ptr<Block> substack)
 {
diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h
index 059d143b..0a23d71d 100644
--- a/src/dev/engine/compiler_p.h
+++ b/src/dev/engine/compiler_p.h
@@ -31,6 +31,7 @@ struct CompilerPrivate
         Target *target = nullptr;
 
         std::shared_ptr<Block> block;
+        int customIfStatementCount = 0;
         std::vector<std::pair<std::pair<std::shared_ptr<Block>, std::shared_ptr<Block>>, SubstackType>> substackTree;
         bool substackHit = false;
         bool warp = false;
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index e20ab74a..822eb358 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -571,6 +571,28 @@ TEST_F(CompilerTest, CreateVariableWrite)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CustomIfStatement)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) {
+        EXPECT_CALL(*m_builder, beginIfStatement());
+        compiler->beginIfStatement();
+        EXPECT_CALL(*m_builder, endIf());
+        compiler->endIf();
+
+        EXPECT_CALL(*m_builder, beginIfStatement());
+        compiler->beginIfStatement();
+        EXPECT_CALL(*m_builder, beginElseBranch());
+        compiler->beginElseBranch();
+        EXPECT_CALL(*m_builder, endIf());
+        compiler->endIf();
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, MoveToIf)
 {
     Compiler compiler(&m_engine, &m_target);

From c304971f11507278c44ad11afa1b7ec09102c472 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Nov 2024 17:24:09 +0100
Subject: [PATCH 042/259] Add list_remove() function

---
 src/scratch/list_functions.cpp               |  5 +++++
 src/scratch/list_functions.h                 |  3 +++
 test/scratch_classes/list_functions_test.cpp | 21 ++++++++++++++++++++
 3 files changed, 29 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index a5ecb988..aaa5a81d 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -12,4 +12,9 @@ extern "C"
     {
         list->clear();
     }
+
+    void list_remove(List *list, size_t index)
+    {
+        list->removeAt(index);
+    }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index 89fc702d..75d75860 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -2,6 +2,8 @@
 
 #pragma once
 
+#include <cstddef>
+
 namespace libscratchcpp
 {
 
@@ -10,6 +12,7 @@ class List;
 extern "C"
 {
     void list_clear(List *list);
+    void list_remove(List *list, size_t index);
 }
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 56a9e3e2..583ea4ec 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -14,3 +14,24 @@ TEST(ListFunctionsTest, Clear)
 
     list_clear(&list);
 }
+
+TEST(ListFunctionsTest, Remove)
+{
+    List list("", "test list");
+    list.append("Lorem");
+    list.append("ipsum");
+    list.append("dolor");
+    list.append("sit");
+    list.append("amet");
+
+    list_remove(&list, 1);
+    ASSERT_EQ(list.toString(), "Lorem dolor sit amet");
+    list_remove(&list, 3);
+    ASSERT_EQ(list.toString(), "Lorem dolor sit");
+    list_remove(&list, 0);
+    ASSERT_EQ(list.toString(), "dolor sit");
+    list_remove(&list, 1);
+    ASSERT_EQ(list.toString(), "dolor");
+    list_remove(&list, 0);
+    ASSERT_TRUE(list.empty());
+}

From 57574c4e4e7a72e81891f3686b1518e797a83582 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Nov 2024 17:40:27 +0100
Subject: [PATCH 043/259] LLVMCodeBuilder: Implement remove list item

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 22 ++++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 42 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 69 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 2e373a36..92f7b02d 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -57,6 +57,7 @@ class ICodeBuilder
         virtual void createVariableWrite(Variable *variable) = 0;
 
         virtual void createListClear(List *list) = 0;
+        virtual void createListRemove(List *list) = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 93606c14..b75b8aee 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -484,6 +484,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::RemoveListItem: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
+                m_builder.CreateCall(resolve_list_remove(), { listPtr.ptr, index });
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -946,6 +955,13 @@ void LLVMCodeBuilder::createListClear(List *list)
     m_listPtrs[list] = LLVMListPtr();
 }
 
+void LLVMCodeBuilder::createListRemove(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::RemoveListItem, Compiler::StaticType::Void, Compiler::StaticType::Number, 1);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
@@ -1842,6 +1858,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_clear()
     return resolveFunction("list_clear", llvm::FunctionType::get(m_builder.getInt1Ty(), { listPtr }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_remove()
+{
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_remove", llvm::FunctionType::get(m_builder.getInt1Ty(), { listPtr, m_builder.getInt64Ty() }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 6fefad72..e576d262 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -63,6 +63,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createVariableWrite(Variable *variable) override;
 
         void createListClear(List *list) override;
+        void createListRemove(List *list) override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -135,6 +136,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_value_greater();
         llvm::FunctionCallee resolve_value_lower();
         llvm::FunctionCallee resolve_list_clear();
+        llvm::FunctionCallee resolve_list_remove();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 300f3c11..2dfce2af 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -43,6 +43,7 @@ struct LLVMInstruction
             WriteVariable,
             ReadVariable,
             ClearList,
+            RemoveListItem,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 61c82a13..a969d9f6 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1974,6 +1974,48 @@ TEST_F(LLVMCodeBuilderTest, ClearList)
     ASSERT_EQ(localList3->toString(), strings[localList3.get()]);
 }
 
+TEST_F(LLVMCodeBuilderTest, RemoveFromList)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    std::unordered_map<List *, std::string> strings;
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+    strings[localList.get()] = localList->toString();
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(1);
+    m_builder->createListRemove(globalList.get());
+
+    m_builder->addConstValue(3);
+    m_builder->createListRemove(localList.get());
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+
+    ASSERT_EQ(globalList->toString(), "13");
+    ASSERT_EQ(localList->toString(), "Lorem ipsum dolor");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 6589d1b0..ee884e21 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -47,6 +47,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createVariableWrite, (Variable *), (override));
 
         MOCK_METHOD(void, createListClear, (List *), (override));
+        MOCK_METHOD(void, createListRemove, (List *), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From ef19574292297c242a5e42ef4a5237c173c5f12f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Nov 2024 18:27:14 +0100
Subject: [PATCH 044/259] LLVMCodeBuilder: Fix return types of list functions

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index b75b8aee..a409e7bd 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1855,13 +1855,13 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower()
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_clear()
 {
     llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    return resolveFunction("list_clear", llvm::FunctionType::get(m_builder.getInt1Ty(), { listPtr }, false));
+    return resolveFunction("list_clear", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_remove()
 {
     llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    return resolveFunction("list_remove", llvm::FunctionType::get(m_builder.getInt1Ty(), { listPtr, m_builder.getInt64Ty() }, false));
+    return resolveFunction("list_remove", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr, m_builder.getInt64Ty() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()

From 4901aa0c5d216fdb97eedec872b4570982f47f93 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Nov 2024 20:27:13 +0100
Subject: [PATCH 045/259] LLVMCodeBuilder: Fix value type field index

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index a409e7bd..1102e42b 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1466,7 +1466,7 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP
                 case Compiler::StaticType::Bool: {
                     // Write number to bool value directly and change type
                     llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
-                    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1);
                     m_builder.CreateStore(converted, ptr);
                     m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typePtr);
                     break;
@@ -1485,7 +1485,7 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP
                     // Write bool to number value directly and change type
                     llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
                     m_builder.CreateStore(converted, ptr);
-                    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+                    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1);
                     m_builder.CreateStore(converted, ptr);
                     m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typePtr);
                     break;

From 9e790d08859cdcb409ec83c63241b71cf2b02336 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Nov 2024 22:03:50 +0100
Subject: [PATCH 046/259] Add value_append_empty() function

---
 src/scratch/list_functions.cpp               |  5 +++++
 src/scratch/list_functions.h                 |  3 +++
 test/scratch_classes/list_functions_test.cpp | 17 +++++++++++++++++
 3 files changed, 25 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index aaa5a81d..e46000f5 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -17,4 +17,9 @@ extern "C"
     {
         list->removeAt(index);
     }
+
+    ValueData *list_append_empty(List *list)
+    {
+        return &list->appendEmpty();
+    }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index 75d75860..40c6c996 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -8,11 +8,14 @@ namespace libscratchcpp
 {
 
 class List;
+struct ValueData;
 
 extern "C"
 {
     void list_clear(List *list);
     void list_remove(List *list, size_t index);
+
+    ValueData *list_append_empty(List *list);
 }
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 583ea4ec..5bc5941e 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -35,3 +35,20 @@ TEST(ListFunctionsTest, Remove)
     list_remove(&list, 0);
     ASSERT_TRUE(list.empty());
 }
+
+TEST(ListFunctionsTest, AppendEmpty)
+{
+    List list("", "test list");
+    list.append("Lorem");
+    list.append("ipsum");
+
+    ValueData *v = list_append_empty(&list);
+    value_init(v);
+    value_assign_double(v, 5);
+    ASSERT_EQ(list.toString(), "Lorem ipsum 5");
+
+    v = list_append_empty(&list);
+    value_init(v);
+    value_assign_string(v, "test");
+    ASSERT_EQ(list.toString(), "Lorem ipsum 5 test");
+}

From fd3cc6c603b30d1cbe367d1cb371458db910d28a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Nov 2024 22:07:44 +0100
Subject: [PATCH 047/259] LLVMCodeBuilder: Implement list append

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 59 +++++++++++++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  3 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 56 ++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 121 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 92f7b02d..7fcbd8d8 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -58,6 +58,7 @@ class ICodeBuilder
 
         virtual void createListClear(List *list) = 0;
         virtual void createListRemove(List *list) = 0;
+        virtual void createListAppend(List *list) = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 1102e42b..36c5aeb7 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -493,6 +493,17 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::AppendToList: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                Compiler::StaticType type = optimizeRegisterType(arg.second);
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr);
+                createInitialValueStore(arg.second, itemPtr, type);
+                // TODO: Implement list type prediction
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -962,6 +973,13 @@ void LLVMCodeBuilder::createListRemove(List *list)
     m_listPtrs[list] = LLVMListPtr();
 }
 
+void LLVMCodeBuilder::createListAppend(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::AppendToList, Compiler::StaticType::Void, Compiler::StaticType::Unknown, 1);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
@@ -1519,6 +1537,41 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP
     }
 }
 
+void LLVMCodeBuilder::createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType)
+{
+    llvm::Value *converted = nullptr;
+
+    if (sourceType != Compiler::StaticType::Unknown)
+        converted = castValue(reg, sourceType);
+
+    auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair<ValueType, Compiler::StaticType> &pair) { return pair.second == sourceType; });
+    const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored
+
+    llvm::Value *valuePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
+    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1);
+    m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typePtr);
+
+    switch (sourceType) {
+        case Compiler::StaticType::Number:
+        case Compiler::StaticType::Bool:
+            // Write number/bool directly
+            m_builder.CreateStore(converted, valuePtr);
+            break;
+
+        case Compiler::StaticType::String:
+            m_builder.CreateCall(resolve_value_assign_cstring(), { targetPtr, converted });
+            break;
+
+        case Compiler::StaticType::Unknown:
+            m_builder.CreateCall(resolve_value_assign_copy(), { targetPtr, reg->value });
+            break;
+
+        default:
+            assert(false);
+            break;
+    }
+}
+
 void LLVMCodeBuilder::createValueCopy(llvm::Value *source, llvm::Value *target)
 {
     // NOTE: This doesn't copy strings, but only the pointers
@@ -1864,6 +1917,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_remove()
     return resolveFunction("list_remove", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr, m_builder.getInt64Ty() }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_append_empty()
+{
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_append_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index e576d262..b32d830c 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -64,6 +64,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void createListClear(List *list) override;
         void createListRemove(List *list) override;
+        void createListAppend(List *list) override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -111,6 +112,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
 
         void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
+        void createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
         llvm::Value *createValue(LLVMRegisterPtr reg);
@@ -137,6 +139,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_value_lower();
         llvm::FunctionCallee resolve_list_clear();
         llvm::FunctionCallee resolve_list_remove();
+        llvm::FunctionCallee resolve_list_append_empty();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 2dfce2af..cd6de717 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -44,6 +44,7 @@ struct LLVMInstruction
             ReadVariable,
             ClearList,
             RemoveListItem,
+            AppendToList,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index a969d9f6..ecdbd8b1 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2016,6 +2016,62 @@ TEST_F(LLVMCodeBuilderTest, RemoveFromList)
     ASSERT_EQ(localList->toString(), "Lorem ipsum dolor");
 }
 
+TEST_F(LLVMCodeBuilderTest, AppendToList)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    std::unordered_map<List *, std::string> strings;
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+    strings[localList.get()] = localList->toString();
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(1);
+    m_builder->createListAppend(globalList.get());
+
+    m_builder->addConstValue("test");
+    m_builder->createListAppend(globalList.get());
+
+    m_builder->addConstValue(3);
+    m_builder->createListAppend(localList.get());
+
+    m_builder->createListClear(localList.get());
+
+    m_builder->addConstValue(true);
+    m_builder->createListAppend(localList.get());
+
+    m_builder->addConstValue(false);
+    m_builder->createListAppend(localList.get());
+
+    m_builder->addConstValue("hello world");
+    m_builder->createListAppend(localList.get());
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+
+    ASSERT_EQ(globalList->toString(), "1 2 3 1 test");
+    ASSERT_EQ(localList->toString(), "true false hello world");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index ee884e21..ee59dbfa 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -48,6 +48,7 @@ class CodeBuilderMock : public ICodeBuilder
 
         MOCK_METHOD(void, createListClear, (List *), (override));
         MOCK_METHOD(void, createListRemove, (List *), (override));
+        MOCK_METHOD(void, createListAppend, (List *), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From f5c18952a33b47516a5d7977144e503ab046f08d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 17 Nov 2024 10:11:32 +0100
Subject: [PATCH 048/259] List: Add insert_empty() method

---
 include/scratchcpp/list.h | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h
index 8ce30c85..715d093c 100644
--- a/include/scratchcpp/list.h
+++ b/include/scratchcpp/list.h
@@ -96,16 +96,19 @@ class LIBSCRATCHCPP_EXPORT List : public Entity
             m_size--;
         }
 
-        /*! Inserts an item at index. */
-        inline void insert(size_t index, const ValueData &value)
+        /*! Inserts an empty item at index and returns the reference to it. Can be used for custom initialization. */
+        inline ValueData &insertEmpty(size_t index)
         {
             assert(index >= 0 && index <= size());
             m_size++;
             reserve(getAllocSize(m_size));
             std::rotate(m_dataPtr->rbegin() + m_dataPtr->size() - m_size, m_dataPtr->rbegin() + m_dataPtr->size() - m_size + 1, m_dataPtr->rend() - index);
-            value_assign_copy(&m_dataPtr->operator[](index), &value);
+            return m_dataPtr->operator[](index);
         }
 
+        /*! Inserts an item at index. */
+        inline void insert(size_t index, const ValueData &value) { value_assign_copy(&insertEmpty(index), &value); }
+
         /*! Inserts an item at index. */
         inline void insert(size_t index, const Value &value) { insert(index, value.data()); }
 

From 138197f805dc456a713974db4b7e1fe6d3be9caf Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 17 Nov 2024 10:12:27 +0100
Subject: [PATCH 049/259] Add list_insert_empty() function

---
 src/scratch/list_functions.cpp               |  5 +++++
 src/scratch/list_functions.h                 |  1 +
 test/scratch_classes/list_functions_test.cpp | 17 +++++++++++++++++
 3 files changed, 23 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index e46000f5..0fc46dc6 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -22,4 +22,9 @@ extern "C"
     {
         return &list->appendEmpty();
     }
+
+    ValueData *list_insert_empty(List *list, size_t index)
+    {
+        return &list->insertEmpty(index);
+    }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index 40c6c996..f9e13924 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -16,6 +16,7 @@ extern "C"
     void list_remove(List *list, size_t index);
 
     ValueData *list_append_empty(List *list);
+    ValueData *list_insert_empty(List *list, size_t index);
 }
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 5bc5941e..93340c75 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -52,3 +52,20 @@ TEST(ListFunctionsTest, AppendEmpty)
     value_assign_string(v, "test");
     ASSERT_EQ(list.toString(), "Lorem ipsum 5 test");
 }
+
+TEST(ListFunctionsTest, InsertEmpty)
+{
+    List list("", "test list");
+    list.append("Lorem");
+    list.append("ipsum");
+
+    ValueData *v = list_insert_empty(&list, 0);
+    value_init(v);
+    value_assign_double(v, 5);
+    ASSERT_EQ(list.toString(), "5 Lorem ipsum");
+
+    v = list_insert_empty(&list, 2);
+    value_init(v);
+    value_assign_string(v, "test");
+    ASSERT_EQ(list.toString(), "5 Lorem test ipsum");
+}

From cf0f44080f7b37684312b0561f5658053b824a7a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 17 Nov 2024 10:38:38 +0100
Subject: [PATCH 050/259] LLVMCodeBuilder: Allow different arg types in
 createOp()

---
 .../engine/internal/llvm/llvmcodebuilder.cpp    | 17 +++++++++++++++--
 src/dev/engine/internal/llvm/llvmcodebuilder.h  |  1 +
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 36c5aeb7..3f9aeab8 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1438,14 +1438,27 @@ void LLVMCodeBuilder::reloadVariables(llvm::Value *targetVariables)
 }
 
 LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
+{
+    std::vector<Compiler::StaticType> types;
+    types.reserve(argCount);
+
+    for (size_t i = 0; i < argCount; i++)
+        types.push_back(argType);
+
+    return createOp(type, retType, types, argCount);
+}
+
+LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector<Compiler::StaticType> &argTypes, size_t argCount)
 {
     LLVMInstruction ins(type);
 
     assert(m_tmpRegs.size() >= argCount);
     size_t j = 0;
 
-    for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++)
-        ins.args.push_back({ argType, m_tmpRegs[i] });
+    for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++) {
+        ins.args.push_back({ argTypes[j], m_tmpRegs[i] });
+        j++;
+    }
 
     m_tmpRegs.erase(m_tmpRegs.end() - argCount, m_tmpRegs.end());
 
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index b32d830c..c786d216 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -110,6 +110,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void reloadVariables(llvm::Value *targetVariables);
 
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
+        LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector<Compiler::StaticType> &argTypes, size_t argCount);
 
         void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
         void createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);

From 14840cfc58b049b0b50b168767fe2bc29e966f54 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 17 Nov 2024 10:38:59 +0100
Subject: [PATCH 051/259] LLVMCodeBuilder: Implement list insert

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 26 ++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 62 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 93 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 7fcbd8d8..77641a01 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -59,6 +59,7 @@ class ICodeBuilder
         virtual void createListClear(List *list) = 0;
         virtual void createListRemove(List *list) = 0;
         virtual void createListAppend(List *list) = 0;
+        virtual void createListInsert(List *list) = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 3f9aeab8..901104fa 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -504,6 +504,19 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::InsertToList: {
+                assert(step.args.size() == 2);
+                const auto &indexArg = step.args[0];
+                const auto &valueArg = step.args[1];
+                Compiler::StaticType type = optimizeRegisterType(valueArg.second);
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
+                llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
+                createInitialValueStore(valueArg.second, itemPtr, type);
+                // TODO: Implement list type prediction
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -980,6 +993,13 @@ void LLVMCodeBuilder::createListAppend(List *list)
     m_listPtrs[list] = LLVMListPtr();
 }
 
+void LLVMCodeBuilder::createListInsert(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::InsertToList, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, 2);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
@@ -1936,6 +1956,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_append_empty()
     return resolveFunction("list_append_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_insert_empty()
+{
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_insert_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index c786d216..9124311b 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -65,6 +65,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createListClear(List *list) override;
         void createListRemove(List *list) override;
         void createListAppend(List *list) override;
+        void createListInsert(List *list) override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -141,6 +142,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_clear();
         llvm::FunctionCallee resolve_list_remove();
         llvm::FunctionCallee resolve_list_append_empty();
+        llvm::FunctionCallee resolve_list_insert_empty();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index cd6de717..2bcd8a76 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -45,6 +45,7 @@ struct LLVMInstruction
             ClearList,
             RemoveListItem,
             AppendToList,
+            InsertToList,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index ecdbd8b1..c3a3af2d 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2072,6 +2072,68 @@ TEST_F(LLVMCodeBuilderTest, AppendToList)
     ASSERT_EQ(localList->toString(), "true false hello world");
 }
 
+TEST_F(LLVMCodeBuilderTest, InsertToList)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    std::unordered_map<List *, std::string> strings;
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+    strings[localList.get()] = localList->toString();
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(2);
+    m_builder->addConstValue(1);
+    m_builder->createListInsert(globalList.get());
+
+    m_builder->addConstValue(3);
+    m_builder->addConstValue("test");
+    m_builder->createListInsert(globalList.get());
+
+    m_builder->addConstValue(0);
+    m_builder->addConstValue(3);
+    m_builder->createListInsert(localList.get());
+
+    m_builder->createListClear(localList.get());
+
+    m_builder->addConstValue(0);
+    m_builder->addConstValue(true);
+    m_builder->createListInsert(localList.get());
+
+    m_builder->addConstValue(0);
+    m_builder->addConstValue(false);
+    m_builder->createListInsert(localList.get());
+
+    m_builder->addConstValue(1);
+    m_builder->addConstValue("hello world");
+    m_builder->createListInsert(localList.get());
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+
+    ASSERT_EQ(globalList->toString(), "1 2 1 test 3");
+    ASSERT_EQ(localList->toString(), "false hello world true");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index ee59dbfa..23e706eb 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -49,6 +49,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createListClear, (List *), (override));
         MOCK_METHOD(void, createListRemove, (List *), (override));
         MOCK_METHOD(void, createListAppend, (List *), (override));
+        MOCK_METHOD(void, createListInsert, (List *), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From 79c605f9083693ea80132a15b6f4f3c23a77d88e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 17 Nov 2024 11:13:23 +0100
Subject: [PATCH 052/259] Add list_get_item() function

---
 src/scratch/list_functions.cpp               |  5 +++++
 src/scratch/list_functions.h                 |  2 ++
 test/scratch_classes/list_functions_test.cpp | 16 ++++++++++++++++
 3 files changed, 23 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index 0fc46dc6..d7ed098d 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -27,4 +27,9 @@ extern "C"
     {
         return &list->insertEmpty(index);
     }
+
+    ValueData *list_get_item(List *list, size_t index)
+    {
+        return &list->operator[](index);
+    }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index f9e13924..ced8c216 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -17,6 +17,8 @@ extern "C"
 
     ValueData *list_append_empty(List *list);
     ValueData *list_insert_empty(List *list, size_t index);
+
+    ValueData *list_get_item(List *list, size_t index);
 }
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 93340c75..5dafc6df 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -69,3 +69,19 @@ TEST(ListFunctionsTest, InsertEmpty)
     value_assign_string(v, "test");
     ASSERT_EQ(list.toString(), "5 Lorem test ipsum");
 }
+
+TEST(ListFunctionsTest, GetItem)
+{
+    List list("", "test list");
+    list.append("Lorem");
+    list.append("ipsum");
+    list.append("dolor");
+    list.append("sit");
+    list.append("amet");
+
+    ASSERT_EQ(list_get_item(&list, 0), &list[0]);
+    ASSERT_EQ(list_get_item(&list, 1), &list[1]);
+    ASSERT_EQ(list_get_item(&list, 2), &list[2]);
+    ASSERT_EQ(list_get_item(&list, 3), &list[3]);
+    ASSERT_EQ(list_get_item(&list, 4), &list[4]);
+}

From aa1d055fb1bada24eca5c0af93157b87262a57f4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 17 Nov 2024 11:13:46 +0100
Subject: [PATCH 053/259] LLVMCodeBuilder: Implement list replace

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 26 +++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 56 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 87 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 77641a01..492aaeeb 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -60,6 +60,7 @@ class ICodeBuilder
         virtual void createListRemove(List *list) = 0;
         virtual void createListAppend(List *list) = 0;
         virtual void createListInsert(List *list) = 0;
+        virtual void createListReplace(List *list) = 0;
 
         virtual void beginIfStatement() = 0;
         virtual void beginElseBranch() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 901104fa..8d3f1708 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -517,6 +517,19 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::ListReplace: {
+                assert(step.args.size() == 2);
+                const auto &indexArg = step.args[0];
+                const auto &valueArg = step.args[1];
+                Compiler::StaticType type = optimizeRegisterType(valueArg.second);
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
+                llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_get_item(), { listPtr.ptr, index });
+                createValueStore(valueArg.second, itemPtr, type, listPtr.type);
+                // TODO: Implement list type prediction
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -1000,6 +1013,13 @@ void LLVMCodeBuilder::createListInsert(List *list)
     m_listPtrs[list] = LLVMListPtr();
 }
 
+void LLVMCodeBuilder::createListReplace(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::ListReplace, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, 2);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::beginIfStatement()
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
@@ -1962,6 +1982,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_insert_empty()
     return resolveFunction("list_insert_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_get_item()
+{
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_get_item", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 9124311b..1a71d4ae 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -66,6 +66,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createListRemove(List *list) override;
         void createListAppend(List *list) override;
         void createListInsert(List *list) override;
+        void createListReplace(List *list) override;
 
         void beginIfStatement() override;
         void beginElseBranch() override;
@@ -143,6 +144,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_remove();
         llvm::FunctionCallee resolve_list_append_empty();
         llvm::FunctionCallee resolve_list_insert_empty();
+        llvm::FunctionCallee resolve_list_get_item();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 2bcd8a76..f57c9571 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -46,6 +46,7 @@ struct LLVMInstruction
             RemoveListItem,
             AppendToList,
             InsertToList,
+            ListReplace,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index c3a3af2d..21cc350b 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2134,6 +2134,62 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
     ASSERT_EQ(localList->toString(), "false hello world true");
 }
 
+TEST_F(LLVMCodeBuilderTest, ListReplace)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    std::unordered_map<List *, std::string> strings;
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+    strings[localList.get()] = localList->toString();
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(2);
+    m_builder->addConstValue(1);
+    m_builder->createListReplace(globalList.get());
+
+    m_builder->addConstValue(1);
+    m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get());
+
+    m_builder->addConstValue(0);
+    m_builder->addConstValue(3);
+    m_builder->createListReplace(localList.get());
+
+    m_builder->addConstValue(2);
+    m_builder->addConstValue(true);
+    m_builder->createListReplace(localList.get());
+
+    m_builder->addConstValue(3);
+    m_builder->addConstValue("hello world");
+    m_builder->createListReplace(localList.get());
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+
+    ASSERT_EQ(globalList->toString(), "1 test 1");
+    ASSERT_EQ(localList->toString(), "3 ipsum true hello world");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 23e706eb..8bd55c33 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -50,6 +50,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, createListRemove, (List *), (override));
         MOCK_METHOD(void, createListAppend, (List *), (override));
         MOCK_METHOD(void, createListInsert, (List *), (override));
+        MOCK_METHOD(void, createListReplace, (List *), (override));
 
         MOCK_METHOD(void, beginIfStatement, (), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));

From d60cbe743dec841b8d05501d568a8f0a95ac7199 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Nov 2024 14:50:17 +0100
Subject: [PATCH 054/259] LLVMCodeBuilder: Implement get list item

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 30 ++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 69 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 103 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 492aaeeb..45cf48c1 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -23,6 +23,7 @@ class ICodeBuilder
         virtual void addConstValue(const Value &value) = 0;
         virtual void addVariableValue(Variable *variable) = 0;
         virtual void addListContents(List *list) = 0;
+        virtual void addListItem(List *list) = 0;
 
         virtual void createAdd() = 0;
         virtual void createSub() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 8d3f1708..bd2c59de 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -530,6 +530,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::GetListItem: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
+                step.functionReturnReg->value = m_builder.CreateCall(resolve_list_get_item(), { listPtr.ptr, index });
+                step.functionReturnReg->type = listPtr.type;
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -848,6 +858,26 @@ void LLVMCodeBuilder::addListContents(List *list)
 {
 }
 
+void LLVMCodeBuilder::addListItem(List *list)
+{
+    LLVMInstruction ins(LLVMInstruction::Type::GetListItem);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+
+    assert(m_tmpRegs.size() >= 1);
+    ins.args.push_back({ Compiler::StaticType::Number, m_tmpRegs[0] });
+
+    m_tmpRegs.erase(m_tmpRegs.end() - 1, m_tmpRegs.end());
+
+    auto ret = std::make_shared<LLVMRegister>(Compiler::StaticType::Unknown);
+    ret->isRawValue = false;
+    ins.functionReturnReg = ret;
+    m_regs[m_currentFunction].push_back(ret);
+    m_tmpRegs.push_back(ret);
+
+    m_instructions.push_back(ins);
+}
+
 void LLVMCodeBuilder::createAdd()
 {
     createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 1a71d4ae..7e20afeb 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -29,6 +29,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void addConstValue(const Value &value) override;
         void addVariableValue(Variable *variable) override;
         void addListContents(List *list) override;
+        void addListItem(List *list) override;
 
         void createAdd() override;
         void createSub() override;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index f57c9571..c6547db3 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -47,6 +47,7 @@ struct LLVMInstruction
             AppendToList,
             InsertToList,
             ListReplace,
+            GetListItem,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 21cc350b..3320b733 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2190,6 +2190,75 @@ TEST_F(LLVMCodeBuilderTest, ListReplace)
     ASSERT_EQ(localList->toString(), "3 ipsum true hello world");
 }
 
+TEST_F(LLVMCodeBuilderTest, GetListItem)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    std::unordered_map<List *, std::string> strings;
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+    strings[localList.get()] = localList->toString();
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(2);
+    m_builder->addListItem(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get());
+
+    m_builder->addConstValue(0);
+    m_builder->addListItem(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(0);
+    m_builder->addListItem(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(2);
+    m_builder->addListItem(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(3);
+    m_builder->addListItem(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    static const std::string expected =
+        "3\n"
+        "1\n"
+        "Lorem\n"
+        "dolor\n"
+        "sit\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+
+    ASSERT_EQ(globalList->toString(), "1 test 3");
+    ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 8bd55c33..61f1c497 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -13,6 +13,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, addConstValue, (const Value &), (override));
         MOCK_METHOD(void, addVariableValue, (Variable *), (override));
         MOCK_METHOD(void, addListContents, (List *), (override));
+        MOCK_METHOD(void, addListItem, (List *), (override));
 
         MOCK_METHOD(void, createAdd, (), (override));
         MOCK_METHOD(void, createSub, (), (override));

From 116121b9bbc75e85922ab95c46247807cf5805aa Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Nov 2024 20:30:27 +0100
Subject: [PATCH 055/259] Add list_size() function

---
 src/scratch/list_functions.cpp               |  5 +++++
 src/scratch/list_functions.h                 |  1 +
 test/scratch_classes/list_functions_test.cpp | 22 ++++++++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index d7ed098d..a8707c72 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -32,4 +32,9 @@ extern "C"
     {
         return &list->operator[](index);
     }
+
+    size_t list_size(List *list)
+    {
+        return list->size();
+    }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index ced8c216..52fd17d8 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -19,6 +19,7 @@ extern "C"
     ValueData *list_insert_empty(List *list, size_t index);
 
     ValueData *list_get_item(List *list, size_t index);
+    size_t list_size(List *list);
 }
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 5dafc6df..09571bbc 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -85,3 +85,25 @@ TEST(ListFunctionsTest, GetItem)
     ASSERT_EQ(list_get_item(&list, 3), &list[3]);
     ASSERT_EQ(list_get_item(&list, 4), &list[4]);
 }
+
+TEST(ListFunctionsTest, Size)
+{
+    {
+        List list("", "");
+        list.append("Lorem");
+        list.append("ipsum");
+        list.append("dolor");
+        list.append("sit");
+        list.append("amet");
+
+        ASSERT_EQ(list_size(&list), 5);
+    }
+
+    {
+        List list("", "");
+        list.append("1");
+        list.append("2");
+
+        ASSERT_EQ(list_size(&list), 2);
+    }
+}

From ae122fc1ef86174bfe4126b633baa4377486948c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 18 Nov 2024 21:57:26 +0100
Subject: [PATCH 056/259] LLVMCodeBuilder: Implement list size

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 20 +++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 45 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 70 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 45cf48c1..8d499c8f 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -24,6 +24,7 @@ class ICodeBuilder
         virtual void addVariableValue(Variable *variable) = 0;
         virtual void addListContents(List *list) = 0;
         virtual void addListItem(List *list) = 0;
+        virtual void addListSize(List *list) = 0;
 
         virtual void createAdd() = 0;
         virtual void createSub() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index bd2c59de..bbd7a05c 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -540,6 +540,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::GetListSize: {
+                assert(step.args.size() == 0);
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                step.functionReturnReg->value = m_builder.CreateUIToFP(m_builder.CreateCall(resolve_list_size(), listPtr.ptr), m_builder.getDoubleTy());
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -878,6 +885,13 @@ void LLVMCodeBuilder::addListItem(List *list)
     m_instructions.push_back(ins);
 }
 
+void LLVMCodeBuilder::addListSize(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListSize, Compiler::StaticType::Number, {}, 0);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::createAdd()
 {
     createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
@@ -2018,6 +2032,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_get_item()
     return resolveFunction("list_get_item", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size()
+{
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_size", llvm::FunctionType::get(m_builder.getInt64Ty(), { listPtr }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 7e20afeb..2fbe38d1 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -30,6 +30,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void addVariableValue(Variable *variable) override;
         void addListContents(List *list) override;
         void addListItem(List *list) override;
+        void addListSize(List *list) override;
 
         void createAdd() override;
         void createSub() override;
@@ -146,6 +147,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_append_empty();
         llvm::FunctionCallee resolve_list_insert_empty();
         llvm::FunctionCallee resolve_list_get_item();
+        llvm::FunctionCallee resolve_list_size();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index c6547db3..70bf7776 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -48,6 +48,7 @@ struct LLVMInstruction
             InsertToList,
             ListReplace,
             GetListItem,
+            GetListSize,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 3320b733..b582f324 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2259,6 +2259,51 @@ TEST_F(LLVMCodeBuilderTest, GetListItem)
     ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
 }
 
+TEST_F(LLVMCodeBuilderTest, GetListSize)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+
+    createBuilder(&sprite, true);
+
+    m_builder->addListSize(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addListSize(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    static const std::string expected =
+        "3\n"
+        "4\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+
+    ASSERT_EQ(globalList->toString(), "123");
+    ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 61f1c497..9d312010 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -14,6 +14,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, addVariableValue, (Variable *), (override));
         MOCK_METHOD(void, addListContents, (List *), (override));
         MOCK_METHOD(void, addListItem, (List *), (override));
+        MOCK_METHOD(void, addListSize, (List *), (override));
 
         MOCK_METHOD(void, createAdd, (), (override));
         MOCK_METHOD(void, createSub, (), (override));

From 1b2a68e6942396b23aac0d3ea660123f0f083f29 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 17:27:27 +0100
Subject: [PATCH 057/259] List: Add data() method

---
 include/scratchcpp/list.h          |  3 +++
 test/scratch_classes/list_test.cpp | 17 +++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h
index 715d093c..ff8d06e2 100644
--- a/include/scratchcpp/list.h
+++ b/include/scratchcpp/list.h
@@ -37,6 +37,9 @@ class LIBSCRATCHCPP_EXPORT List : public Entity
         Monitor *monitor() const;
         void setMonitor(Monitor *monitor);
 
+        /*! Returns a pointer to the raw list data. */
+        inline ValueData *data() const { return m_dataPtr->data(); }
+
         /*! Returns the list size. */
         inline size_t size() const { return m_size; }
 
diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp
index a6574b5d..458eebf9 100644
--- a/test/scratch_classes/list_test.cpp
+++ b/test/scratch_classes/list_test.cpp
@@ -40,6 +40,23 @@ TEST(ListTest, Monitor)
     ASSERT_EQ(list.monitor(), &monitor);
 }
 
+TEST(ListTest, Data)
+{
+    List list("", "");
+    list.append("Lorem");
+    list.append("ipsum");
+    list.append("dolor");
+    list.append("sit");
+    list.append("amet");
+
+    ValueData *data = list.data();
+    ASSERT_EQ(&data[0], &list[0]);
+    ASSERT_EQ(&data[1], &list[1]);
+    ASSERT_EQ(&data[2], &list[2]);
+    ASSERT_EQ(&data[3], &list[3]);
+    ASSERT_EQ(&data[4], &list[4]);
+}
+
 TEST(ListTest, Size)
 {
     List list("", "test list");

From cd86c359a11979e32e13c9578fa9ffde818ed0ef Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 17:28:20 +0100
Subject: [PATCH 058/259] Add list_data() function

---
 src/scratch/list_functions.cpp               |  5 +++++
 src/scratch/list_functions.h                 |  1 +
 test/scratch_classes/list_functions_test.cpp | 13 +++++++++++++
 3 files changed, 19 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index a8707c72..4bdb58c1 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -33,6 +33,11 @@ extern "C"
         return &list->operator[](index);
     }
 
+    ValueData *list_data(List *list)
+    {
+        return list->data();
+    }
+
     size_t list_size(List *list)
     {
         return list->size();
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index 52fd17d8..876ac638 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -19,6 +19,7 @@ extern "C"
     ValueData *list_insert_empty(List *list, size_t index);
 
     ValueData *list_get_item(List *list, size_t index);
+    ValueData *list_data(List *list);
     size_t list_size(List *list);
 }
 
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 09571bbc..bc7dfea8 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -86,6 +86,19 @@ TEST(ListFunctionsTest, GetItem)
     ASSERT_EQ(list_get_item(&list, 4), &list[4]);
 }
 
+TEST(ListFunctionsTest, Data)
+{
+    List list("", "");
+    list.append("Lorem");
+    list.append("ipsum");
+    list.append("dolor");
+    list.append("sit");
+    list.append("amet");
+
+    ValueData *data = list_data(&list);
+    ASSERT_EQ(data, list.data());
+}
+
 TEST(ListFunctionsTest, Size)
 {
     {

From 20d6b84e7a18b41d2aad7071e67637a989666e54 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 17:29:41 +0100
Subject: [PATCH 059/259] LLVMTypes: Add missing padding to ValueData struct

---
 src/dev/engine/internal/llvm/llvmtypes.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmtypes.cpp b/src/dev/engine/internal/llvm/llvmtypes.cpp
index bbd21f28..5dfc6075 100644
--- a/src/dev/engine/internal/llvm/llvmtypes.cpp
+++ b/src/dev/engine/internal/llvm/llvmtypes.cpp
@@ -14,10 +14,11 @@ llvm::StructType *LLVMTypes::createValueDataType(llvm::IRBuilder<> *builder)
     llvm::Type *unionType = builder->getInt64Ty(); // 64 bits is the largest size
 
     llvm::Type *valueType = llvm::Type::getInt32Ty(ctx); // Assuming ValueType is a 32-bit enum
+    llvm::Type *padding = llvm::Type::getInt32Ty(ctx);   // Padding for alignment
     llvm::Type *sizeType = llvm::Type::getInt64Ty(ctx);  // size_t
 
     llvm::StructType *ret = llvm::StructType::create(ctx, "ValueData");
-    ret->setBody({ unionType, valueType, sizeType });
+    ret->setBody({ unionType, valueType, padding, sizeType });
 
     return ret;
 }

From cc397a51492523456e47eb39c4bb367659f37895 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 17:32:29 +0100
Subject: [PATCH 060/259] LLVMCodeBuilder: Use data pointer to access list data

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 28 +++++++++++++++----
 .../engine/internal/llvm/llvmcodebuilder.h    |  4 ++-
 src/dev/engine/internal/llvm/llvmlistptr.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        |  4 +--
 4 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index bbd7a05c..7db3e7b1 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -96,8 +96,11 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_scopeVariables.push_back({});
 
     // Create list pointers
-    for (auto &[list, listPtr] : m_listPtrs)
+    for (auto &[list, listPtr] : m_listPtrs) {
         listPtr.ptr = getListPtr(targetLists, list);
+        listPtr.dataPtr = m_builder.CreateAlloca(m_valueDataType->getPointerTo());
+        m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
+    }
 
     // Execute recorded steps
     for (const LLVMInstruction &step : m_instructions) {
@@ -500,6 +503,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr);
                 createInitialValueStore(arg.second, itemPtr, type);
+                m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
                 // TODO: Implement list type prediction
                 break;
             }
@@ -513,6 +517,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
                 llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
                 createInitialValueStore(valueArg.second, itemPtr, type);
+                m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
                 // TODO: Implement list type prediction
                 break;
             }
@@ -524,7 +529,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
-                llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_get_item(), { listPtr.ptr, index });
+                llvm::Value *itemPtr = getListItem(m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
                 createValueStore(valueArg.second, itemPtr, type, listPtr.type);
                 // TODO: Implement list type prediction
                 break;
@@ -535,7 +540,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
-                step.functionReturnReg->value = m_builder.CreateCall(resolve_list_get_item(), { listPtr.ptr, index });
+                step.functionReturnReg->value = getListItem(m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
                 step.functionReturnReg->type = listPtr.type;
                 break;
             }
@@ -551,6 +556,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 if (!m_warp) {
                     freeHeap();
                     syncVariables(targetVariables);
+                    reloadLists();
                     coro->createSuspend();
                     reloadVariables(targetVariables);
                 }
@@ -1521,6 +1527,13 @@ void LLVMCodeBuilder::reloadVariables(llvm::Value *targetVariables)
     }
 }
 
+void LLVMCodeBuilder::reloadLists()
+{
+    // Reload list data pointers
+    for (auto &[list, listPtr] : m_listPtrs)
+        m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
+}
+
 LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
 {
     std::vector<Compiler::StaticType> types;
@@ -1684,6 +1697,11 @@ void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target,
     m_builder.CreateStore(m_builder.CreateLoad(fieldType, sourceField), targetField);
 }
 
+llvm::Value *LLVMCodeBuilder::getListItem(llvm::Value *dataPtr, llvm::Value *index)
+{
+    return m_builder.CreateGEP(m_valueDataType, dataPtr, index);
+}
+
 llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
 {
     if (reg->isConstValue) {
@@ -2026,10 +2044,10 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_insert_empty()
     return resolveFunction("list_insert_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false));
 }
 
-llvm::FunctionCallee LLVMCodeBuilder::resolve_list_get_item()
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_data()
 {
     llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    return resolveFunction("list_get_item", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false));
+    return resolveFunction("list_data", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size()
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 2fbe38d1..6811d849 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -112,6 +112,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *getListPtr(llvm::Value *targetLists, List *list);
         void syncVariables(llvm::Value *targetVariables);
         void reloadVariables(llvm::Value *targetVariables);
+        void reloadLists();
 
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector<Compiler::StaticType> &argTypes, size_t argCount);
@@ -120,6 +121,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
+        llvm::Value *getListItem(llvm::Value *dataPtr, llvm::Value *index);
         llvm::Value *createValue(LLVMRegisterPtr reg);
         llvm::Value *createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type);
 
@@ -146,7 +148,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_remove();
         llvm::FunctionCallee resolve_list_append_empty();
         llvm::FunctionCallee resolve_list_insert_empty();
-        llvm::FunctionCallee resolve_list_get_item();
+        llvm::FunctionCallee resolve_list_data();
         llvm::FunctionCallee resolve_list_size();
         llvm::FunctionCallee resolve_strcasecmp();
 
diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h
index 9dc93ec4..2df1ddf3 100644
--- a/src/dev/engine/internal/llvm/llvmlistptr.h
+++ b/src/dev/engine/internal/llvm/llvmlistptr.h
@@ -17,6 +17,7 @@ namespace libscratchcpp
 struct LLVMListPtr
 {
         llvm::Value *ptr = nullptr;
+        llvm::Value *dataPtr = nullptr;
         Compiler::StaticType type = Compiler::StaticType::Unknown;
 };
 
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index b582f324..a933298b 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2134,7 +2134,7 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
     ASSERT_EQ(localList->toString(), "false hello world true");
 }
 
-TEST_F(LLVMCodeBuilderTest, ListReplace)
+/*TEST_F(LLVMCodeBuilderTest, ListReplace)
 {
     EngineMock engine;
     Stage stage;
@@ -2188,7 +2188,7 @@ TEST_F(LLVMCodeBuilderTest, ListReplace)
 
     ASSERT_EQ(globalList->toString(), "1 test 1");
     ASSERT_EQ(localList->toString(), "3 ipsum true hello world");
-}
+}*/
 
 TEST_F(LLVMCodeBuilderTest, GetListItem)
 {

From f9d1178b1b15fcf1aa1e9a60e78f8bac79c7740a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 17:32:58 +0100
Subject: [PATCH 061/259] Target: Free list data array

---
 src/scratch/target.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index aa555d74..d1e4224f 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -34,6 +34,9 @@ Target::~Target()
 {
     if (impl->variableData)
         free(impl->variableData);
+
+    if (impl->listData)
+        free(impl->listData);
 }
 
 /*! Returns true. */

From 8e559f1d54c0e2ffc5bed351dfe5d160ac0557b4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 17:57:38 +0100
Subject: [PATCH 062/259] List: Add sizePtr() method

---
 include/scratchcpp/list.h          |  6 ++++++
 test/scratch_classes/list_test.cpp | 25 +++++++++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h
index ff8d06e2..192939bc 100644
--- a/include/scratchcpp/list.h
+++ b/include/scratchcpp/list.h
@@ -40,6 +40,12 @@ class LIBSCRATCHCPP_EXPORT List : public Entity
         /*! Returns a pointer to the raw list data. */
         inline ValueData *data() const { return m_dataPtr->data(); }
 
+        /*!
+         * Returns a pointer to the list size.
+         * \note This is used internally by compiled code for various optimizations.
+         */
+        inline size_t *sizePtr() { return &m_size; }
+
         /*! Returns the list size. */
         inline size_t size() const { return m_size; }
 
diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp
index 458eebf9..62263a41 100644
--- a/test/scratch_classes/list_test.cpp
+++ b/test/scratch_classes/list_test.cpp
@@ -57,6 +57,31 @@ TEST(ListTest, Data)
     ASSERT_EQ(&data[4], &list[4]);
 }
 
+TEST(ListTest, SizePtr)
+{
+    List list("", "test list");
+    ASSERT_TRUE(list.sizePtr());
+    ASSERT_EQ(*list.sizePtr(), 0);
+    ASSERT_TRUE(list.empty());
+
+    list.append("Lorem");
+    list.append("ipsum");
+    ASSERT_EQ(*list.sizePtr(), 2);
+    ASSERT_FALSE(list.empty());
+
+    list.append("dolor");
+    ASSERT_EQ(*list.sizePtr(), 3);
+    ASSERT_FALSE(list.empty());
+
+    list.removeAt(0);
+    ASSERT_EQ(*list.sizePtr(), 2);
+    ASSERT_FALSE(list.empty());
+
+    *list.sizePtr() = 100;
+    ASSERT_EQ(*list.sizePtr(), 100);
+    ASSERT_EQ(list.size(), 100);
+}
+
 TEST(ListTest, Size)
 {
     List list("", "test list");

From 06bb0b182e8c9b813992c0455e8f4d749facd1dd Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 18:00:10 +0100
Subject: [PATCH 063/259] Add list_size_ptr() function

---
 src/scratch/list_functions.cpp               |  5 +++++
 src/scratch/list_functions.h                 |  1 +
 test/scratch_classes/list_functions_test.cpp | 13 +++++++++++++
 3 files changed, 19 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index 4bdb58c1..7b155f1b 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -42,4 +42,9 @@ extern "C"
     {
         return list->size();
     }
+
+    size_t *list_size_ptr(List *list)
+    {
+        return list->sizePtr();
+    }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index 876ac638..70f5b5d7 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -20,6 +20,7 @@ extern "C"
 
     ValueData *list_get_item(List *list, size_t index);
     ValueData *list_data(List *list);
+    size_t *list_size_ptr(List *list);
     size_t list_size(List *list);
 }
 
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index bc7dfea8..7fc158f6 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -99,6 +99,19 @@ TEST(ListFunctionsTest, Data)
     ASSERT_EQ(data, list.data());
 }
 
+TEST(ListFunctionsTest, SizePtr)
+{
+    List list("", "");
+    list.append("Lorem");
+    list.append("ipsum");
+    list.append("dolor");
+    list.append("sit");
+    list.append("amet");
+
+    size_t *sizePtr = list_size_ptr(&list);
+    ASSERT_EQ(sizePtr, list.sizePtr());
+}
+
 TEST(ListFunctionsTest, Size)
 {
     {

From 19b76af010cf8f46d53d145f68fd0e50e8b9d078 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 18:00:42 +0100
Subject: [PATCH 064/259] LLVMCodeBuilder: Optimize list size

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 9 ++++++---
 src/dev/engine/internal/llvm/llvmcodebuilder.h   | 2 +-
 src/dev/engine/internal/llvm/llvmlistptr.h       | 1 +
 3 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 7db3e7b1..d7d42910 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -100,6 +100,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
         listPtr.ptr = getListPtr(targetLists, list);
         listPtr.dataPtr = m_builder.CreateAlloca(m_valueDataType->getPointerTo());
         m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
+        listPtr.sizePtr = m_builder.CreateAlloca(m_builder.getInt64Ty()->getPointerTo());
+        m_builder.CreateStore(m_builder.CreateCall(resolve_list_size_ptr(), listPtr.ptr), listPtr.sizePtr);
     }
 
     // Execute recorded steps
@@ -548,7 +550,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::GetListSize: {
                 assert(step.args.size() == 0);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                step.functionReturnReg->value = m_builder.CreateUIToFP(m_builder.CreateCall(resolve_list_size(), listPtr.ptr), m_builder.getDoubleTy());
+                llvm::Value *sizePtr = m_builder.CreateLoad(m_builder.getInt64Ty()->getPointerTo(), listPtr.sizePtr);
+                step.functionReturnReg->value = m_builder.CreateUIToFP(m_builder.CreateLoad(m_builder.getInt64Ty(), sizePtr), m_builder.getDoubleTy());
                 break;
             }
 
@@ -2050,10 +2053,10 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_data()
     return resolveFunction("list_data", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
 }
 
-llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size()
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size_ptr()
 {
     llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    return resolveFunction("list_size", llvm::FunctionType::get(m_builder.getInt64Ty(), { listPtr }, false));
+    return resolveFunction("list_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 6811d849..d84669ba 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -149,7 +149,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_append_empty();
         llvm::FunctionCallee resolve_list_insert_empty();
         llvm::FunctionCallee resolve_list_data();
-        llvm::FunctionCallee resolve_list_size();
+        llvm::FunctionCallee resolve_list_size_ptr();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h
index 2df1ddf3..e734664e 100644
--- a/src/dev/engine/internal/llvm/llvmlistptr.h
+++ b/src/dev/engine/internal/llvm/llvmlistptr.h
@@ -18,6 +18,7 @@ struct LLVMListPtr
 {
         llvm::Value *ptr = nullptr;
         llvm::Value *dataPtr = nullptr;
+        llvm::Value *sizePtr = nullptr;
         Compiler::StaticType type = Compiler::StaticType::Unknown;
 };
 

From 0ff30d023cd2f198968005c200b071730f3bc9d6 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 18:12:11 +0100
Subject: [PATCH 065/259] Add sizePtr() method to veque

---
 include/scratchcpp/veque.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/include/scratchcpp/veque.h b/include/scratchcpp/veque.h
index f75877c7..a994ebc6 100644
--- a/include/scratchcpp/veque.h
+++ b/include/scratchcpp/veque.h
@@ -375,6 +375,11 @@ namespace veque
             return _size;
         }
 
+        // For libscratchcpp List
+        inline const size_type *sizePtr() const noexcept {
+            return &_size;
+        }
+
         size_type max_size() const noexcept
         {
             constexpr auto compile_time_limit = std::min(

From 3a8bbb44e4c22728c8fdb4803bfe5a08929c31ef Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 21:21:03 +0100
Subject: [PATCH 066/259] List: Add allocatedSizePtr() method

---
 include/scratchcpp/list.h          |  6 ++++++
 test/scratch_classes/list_test.cpp | 17 +++++++++++++++++
 2 files changed, 23 insertions(+)

diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h
index 192939bc..ad85b3a3 100644
--- a/include/scratchcpp/list.h
+++ b/include/scratchcpp/list.h
@@ -46,6 +46,12 @@ class LIBSCRATCHCPP_EXPORT List : public Entity
          */
         inline size_t *sizePtr() { return &m_size; }
 
+        /*!
+         * Returns a pointer to the allocated list size.
+         * \note This is used internally by compiled code for various optimizations.
+         */
+        inline const size_t *allocatedSizePtr() const { return m_dataPtr->sizePtr(); }
+
         /*! Returns the list size. */
         inline size_t size() const { return m_size; }
 
diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp
index 62263a41..587996e8 100644
--- a/test/scratch_classes/list_test.cpp
+++ b/test/scratch_classes/list_test.cpp
@@ -62,6 +62,7 @@ TEST(ListTest, SizePtr)
     List list("", "test list");
     ASSERT_TRUE(list.sizePtr());
     ASSERT_EQ(*list.sizePtr(), 0);
+    const size_t *ptr = list.sizePtr();
     ASSERT_TRUE(list.empty());
 
     list.append("Lorem");
@@ -71,6 +72,7 @@ TEST(ListTest, SizePtr)
 
     list.append("dolor");
     ASSERT_EQ(*list.sizePtr(), 3);
+    ASSERT_EQ(list.sizePtr(), ptr);
     ASSERT_FALSE(list.empty());
 
     list.removeAt(0);
@@ -82,6 +84,21 @@ TEST(ListTest, SizePtr)
     ASSERT_EQ(list.size(), 100);
 }
 
+TEST(ListTest, AllocatedSizePtr)
+{
+    List list("", "test list");
+    ASSERT_TRUE(list.allocatedSizePtr());
+    ASSERT_EQ(*list.allocatedSizePtr(), 0);
+    const size_t *ptr = list.allocatedSizePtr();
+
+    list.append("Lorem");
+    list.append("ipsum");
+
+    ASSERT_GT(*list.allocatedSizePtr(), 0);
+    ASSERT_EQ(list.allocatedSizePtr(), ptr);
+    ASSERT_NE(list.allocatedSizePtr(), list.sizePtr());
+}
+
 TEST(ListTest, Size)
 {
     List list("", "test list");

From 2f2ed1453e6fb4472254609164eb49733e5fe2c1 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 21:21:33 +0100
Subject: [PATCH 067/259] Add list_alloc_size_ptr() function

---
 src/scratch/list_functions.cpp               | 13 +++++++++----
 src/scratch/list_functions.h                 |  1 +
 test/scratch_classes/list_functions_test.cpp | 13 +++++++++++++
 3 files changed, 23 insertions(+), 4 deletions(-)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index 7b155f1b..a10cb669 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -38,13 +38,18 @@ extern "C"
         return list->data();
     }
 
-    size_t list_size(List *list)
+    size_t *list_size_ptr(List *list)
     {
-        return list->size();
+        return list->sizePtr();
     }
 
-    size_t *list_size_ptr(List *list)
+    const size_t *list_alloc_size_ptr(List *list)
     {
-        return list->sizePtr();
+        return list->allocatedSizePtr();
+    }
+
+    size_t list_size(List *list)
+    {
+        return list->size();
     }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index 70f5b5d7..05edc4c4 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -21,6 +21,7 @@ extern "C"
     ValueData *list_get_item(List *list, size_t index);
     ValueData *list_data(List *list);
     size_t *list_size_ptr(List *list);
+    const size_t *list_alloc_size_ptr(List *list);
     size_t list_size(List *list);
 }
 
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 7fc158f6..2f10e80f 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -112,6 +112,19 @@ TEST(ListFunctionsTest, SizePtr)
     ASSERT_EQ(sizePtr, list.sizePtr());
 }
 
+TEST(ListFunctionsTest, AllocSizePtr)
+{
+    List list("", "");
+    list.append("Lorem");
+    list.append("ipsum");
+    list.append("dolor");
+    list.append("sit");
+    list.append("amet");
+
+    const size_t *sizePtr = list_alloc_size_ptr(&list);
+    ASSERT_EQ(sizePtr, list.allocatedSizePtr());
+}
+
 TEST(ListFunctionsTest, Size)
 {
     {

From 94fc6b17c9b29af5f7d5bae4d461de1ebb3f6090 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 19 Nov 2024 21:38:35 +0100
Subject: [PATCH 068/259] LLVMCodeBuilder: Do not use alloca for list size

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index d7d42910..3d1c67a8 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -100,8 +100,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
         listPtr.ptr = getListPtr(targetLists, list);
         listPtr.dataPtr = m_builder.CreateAlloca(m_valueDataType->getPointerTo());
         m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
-        listPtr.sizePtr = m_builder.CreateAlloca(m_builder.getInt64Ty()->getPointerTo());
-        m_builder.CreateStore(m_builder.CreateCall(resolve_list_size_ptr(), listPtr.ptr), listPtr.sizePtr);
+        listPtr.sizePtr = m_builder.CreateCall(resolve_list_size_ptr(), listPtr.ptr);
     }
 
     // Execute recorded steps
@@ -550,8 +549,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::GetListSize: {
                 assert(step.args.size() == 0);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                llvm::Value *sizePtr = m_builder.CreateLoad(m_builder.getInt64Ty()->getPointerTo(), listPtr.sizePtr);
-                step.functionReturnReg->value = m_builder.CreateUIToFP(m_builder.CreateLoad(m_builder.getInt64Ty(), sizePtr), m_builder.getDoubleTy());
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+                step.functionReturnReg->value = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());
                 break;
             }
 

From d429e0a27f45a68e55c77425512e2fefbfd7bfe2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 20 Nov 2024 08:03:09 +0100
Subject: [PATCH 069/259] LLVMCodeBuilder: Optimize list append

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 34 ++++++++++++++++++-
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 +
 src/dev/engine/internal/llvm/llvmlistptr.h    |  1 +
 3 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 3d1c67a8..c833da4d 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -98,9 +98,12 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     // Create list pointers
     for (auto &[list, listPtr] : m_listPtrs) {
         listPtr.ptr = getListPtr(targetLists, list);
+
         listPtr.dataPtr = m_builder.CreateAlloca(m_valueDataType->getPointerTo());
         m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
+
         listPtr.sizePtr = m_builder.CreateCall(resolve_list_size_ptr(), listPtr.ptr);
+        listPtr.allocatedSizePtr = m_builder.CreateCall(resolve_list_alloc_size_ptr(), listPtr.ptr);
     }
 
     // Execute recorded steps
@@ -502,9 +505,32 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 Compiler::StaticType type = optimizeRegisterType(arg.second);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr);
+
+                // Check if enough space is allocated
+                llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+                llvm::Value *isAllocated = m_builder.CreateICmpUGT(allocatedSize, size);
+                llvm::BasicBlock *ifBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *elseBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+                m_builder.CreateCondBr(isAllocated, ifBlock, elseBlock);
+
+                // If there's enough space, use the allocated memory
+                m_builder.SetInsertPoint(ifBlock);
+                llvm::Value *itemPtr = getListItem(m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), size);
+                createInitialValueStore(arg.second, itemPtr, type);
+                m_builder.CreateStore(m_builder.CreateAdd(size, m_builder.getInt64(1)), listPtr.sizePtr);
+                m_builder.CreateBr(nextBlock);
+
+                // Otherwise call appendEmpty()
+                m_builder.SetInsertPoint(elseBlock);
+                itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr);
                 createInitialValueStore(arg.second, itemPtr, type);
+                // TODO: Update list data only when needed
                 m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
+                m_builder.CreateBr(nextBlock);
+
+                m_builder.SetInsertPoint(nextBlock);
                 // TODO: Implement list type prediction
                 break;
             }
@@ -2058,6 +2084,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size_ptr()
     return resolveFunction("list_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_alloc_size_ptr()
+{
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_alloc_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index d84669ba..c41cbe20 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -150,6 +150,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_insert_empty();
         llvm::FunctionCallee resolve_list_data();
         llvm::FunctionCallee resolve_list_size_ptr();
+        llvm::FunctionCallee resolve_list_alloc_size_ptr();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h
index e734664e..c10ba9ea 100644
--- a/src/dev/engine/internal/llvm/llvmlistptr.h
+++ b/src/dev/engine/internal/llvm/llvmlistptr.h
@@ -19,6 +19,7 @@ struct LLVMListPtr
         llvm::Value *ptr = nullptr;
         llvm::Value *dataPtr = nullptr;
         llvm::Value *sizePtr = nullptr;
+        llvm::Value *allocatedSizePtr = nullptr;
         Compiler::StaticType type = Compiler::StaticType::Unknown;
 };
 

From f982aaed21b27fd9a17b17d70a0ed693e39f6c16 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 20 Nov 2024 09:36:00 +0100
Subject: [PATCH 070/259] LLVMCodeBuilder: Update list data pointer when needed

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 37 +++++++++++++++----
 .../engine/internal/llvm/llvmcodebuilder.h    |  3 +-
 src/dev/engine/internal/llvm/llvmlistptr.h    |  1 +
 3 files changed, 32 insertions(+), 9 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index c833da4d..649e9b57 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -104,6 +104,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
         listPtr.sizePtr = m_builder.CreateCall(resolve_list_size_ptr(), listPtr.ptr);
         listPtr.allocatedSizePtr = m_builder.CreateCall(resolve_list_alloc_size_ptr(), listPtr.ptr);
+
+        listPtr.dataPtrDirty = m_builder.CreateAlloca(m_builder.getInt1Ty());
+        m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
     }
 
     // Execute recorded steps
@@ -488,6 +491,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 0);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 m_builder.CreateCall(resolve_list_clear(), listPtr.ptr);
+                // NOTE: Clearing doesn't deallocate (see List::clear()), so there's no need to update the data pointer
                 break;
             }
 
@@ -497,6 +501,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
                 m_builder.CreateCall(resolve_list_remove(), { listPtr.ptr, index });
+                // NOTE: Removing doesn't deallocate (see List::removeAt()), so there's no need to update the data pointer
                 break;
             }
 
@@ -517,7 +522,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // If there's enough space, use the allocated memory
                 m_builder.SetInsertPoint(ifBlock);
-                llvm::Value *itemPtr = getListItem(m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), size);
+                llvm::Value *itemPtr = getListItem(listPtr, size, func);
                 createInitialValueStore(arg.second, itemPtr, type);
                 m_builder.CreateStore(m_builder.CreateAdd(size, m_builder.getInt64(1)), listPtr.sizePtr);
                 m_builder.CreateBr(nextBlock);
@@ -526,8 +531,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.SetInsertPoint(elseBlock);
                 itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr);
                 createInitialValueStore(arg.second, itemPtr, type);
-                // TODO: Update list data only when needed
-                m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
+                m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty);
                 m_builder.CreateBr(nextBlock);
 
                 m_builder.SetInsertPoint(nextBlock);
@@ -541,10 +545,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &valueArg = step.args[1];
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+
+                // dataPtrDirty
+                llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+                m_builder.CreateStore(m_builder.CreateICmpEQ(allocatedSize, size), listPtr.dataPtrDirty);
+
+                // Insert
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
                 llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
                 createInitialValueStore(valueArg.second, itemPtr, type);
-                m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
                 // TODO: Implement list type prediction
                 break;
             }
@@ -556,7 +566,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
-                llvm::Value *itemPtr = getListItem(m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
+                llvm::Value *itemPtr = getListItem(listPtr, index, func);
                 createValueStore(valueArg.second, itemPtr, type, listPtr.type);
                 // TODO: Implement list type prediction
                 break;
@@ -567,7 +577,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
-                step.functionReturnReg->value = getListItem(m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
+                step.functionReturnReg->value = getListItem(listPtr, index, func);
                 step.functionReturnReg->type = listPtr.type;
                 break;
             }
@@ -1562,6 +1572,16 @@ void LLVMCodeBuilder::reloadLists()
         m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
 }
 
+void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func)
+{
+    // dataPtr = dirty ? list_data(list) : dataPtr
+    // dirty = false
+    llvm::Value *dirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
+    llvm::Value *dataPtr = m_builder.CreateSelect(dirty, m_builder.CreateCall(resolve_list_data(), listPtr.ptr), m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr));
+    m_builder.CreateStore(dataPtr, listPtr.dataPtr);
+    m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
+}
+
 LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
 {
     std::vector<Compiler::StaticType> types;
@@ -1725,9 +1745,10 @@ void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target,
     m_builder.CreateStore(m_builder.CreateLoad(fieldType, sourceField), targetField);
 }
 
-llvm::Value *LLVMCodeBuilder::getListItem(llvm::Value *dataPtr, llvm::Value *index)
+llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func)
 {
-    return m_builder.CreateGEP(m_valueDataType, dataPtr, index);
+    updateListDataPtr(listPtr, func);
+    return m_builder.CreateGEP(m_valueDataType, m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
 }
 
 llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index c41cbe20..a83affa9 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -113,6 +113,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void syncVariables(llvm::Value *targetVariables);
         void reloadVariables(llvm::Value *targetVariables);
         void reloadLists();
+        void updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func);
 
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector<Compiler::StaticType> &argTypes, size_t argCount);
@@ -121,7 +122,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
-        llvm::Value *getListItem(llvm::Value *dataPtr, llvm::Value *index);
+        llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func);
         llvm::Value *createValue(LLVMRegisterPtr reg);
         llvm::Value *createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type);
 
diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h
index c10ba9ea..6b427572 100644
--- a/src/dev/engine/internal/llvm/llvmlistptr.h
+++ b/src/dev/engine/internal/llvm/llvmlistptr.h
@@ -20,6 +20,7 @@ struct LLVMListPtr
         llvm::Value *dataPtr = nullptr;
         llvm::Value *sizePtr = nullptr;
         llvm::Value *allocatedSizePtr = nullptr;
+        llvm::Value *dataPtrDirty = nullptr;
         Compiler::StaticType type = Compiler::StaticType::Unknown;
 };
 

From e139e3ca83d23ecc495bc08885635ff35ee893a9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Nov 2024 14:18:30 +0100
Subject: [PATCH 071/259] LLVMCodeBuilder: Add missing ValueData padding

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 649e9b57..0b418d41 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1735,7 +1735,8 @@ void LLVMCodeBuilder::createValueCopy(llvm::Value *source, llvm::Value *target)
     // NOTE: This doesn't copy strings, but only the pointers
     copyStructField(source, target, 0, m_valueDataType, m_builder.getInt64Ty()); // value
     copyStructField(source, target, 1, m_valueDataType, m_builder.getInt32Ty()); // type
-    copyStructField(source, target, 2, m_valueDataType, m_builder.getInt64Ty()); // string size
+    /* 2: padding */
+    copyStructField(source, target, 3, m_valueDataType, m_builder.getInt64Ty()); // string size
 }
 
 void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType)
@@ -1764,7 +1765,8 @@ llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
             value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0));
 
         llvm::Constant *type = m_builder.getInt32(static_cast<uint32_t>(reg->constValue.type()));
-        llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, m_builder.getInt64(0) });
+        llvm::Constant *padding = m_builder.getInt32(0);
+        llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, padding, m_builder.getInt64(0) });
         m_builder.CreateStore(constValue, ret);
 
         return ret;

From b1319ed129a687dbb463122c877cf8ca457aa913 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Nov 2024 14:23:04 +0100
Subject: [PATCH 072/259] LLVMCodeBuilder: Fix ValueData constants

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 22 +++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 0b418d41..647f88f5 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1759,10 +1759,24 @@ llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
         llvm::Constant *value = castConstValue(reg->constValue, TYPE_MAP[reg->constValue.type()]);
         llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType);
 
-        if (reg->constValue.type() == ValueType::String)
-            value = llvm::ConstantExpr::getPtrToInt(value, m_valueDataType->getElementType(0));
-        else
-            value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0));
+        switch (reg->constValue.type()) {
+            case ValueType::Number:
+                value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0));
+                break;
+
+            case ValueType::Bool:
+                // Assuming union type is int64
+                value = m_builder.getInt64(reg->constValue.toBool());
+                break;
+
+            case ValueType::String:
+                value = llvm::ConstantExpr::getPtrToInt(value, m_valueDataType->getElementType(0));
+                break;
+
+            default:
+                assert(false);
+                break;
+        }
 
         llvm::Constant *type = m_builder.getInt32(static_cast<uint32_t>(reg->constValue.type()));
         llvm::Constant *padding = m_builder.getInt32(0);

From b05996c9dc18302e9735845dd02c45c2e2f50a9f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Nov 2024 14:24:02 +0100
Subject: [PATCH 073/259] LLVMCodeBuilder: Implement list item index

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 65 +++++++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 91 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 161 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 8d499c8f..4d602122 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -24,6 +24,7 @@ class ICodeBuilder
         virtual void addVariableValue(Variable *variable) = 0;
         virtual void addListContents(List *list) = 0;
         virtual void addListItem(List *list) = 0;
+        virtual void addListItemIndex(List *list) = 0;
         virtual void addListSize(List *list) = 0;
 
         virtual void createAdd() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 647f88f5..8cc39abf 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -590,6 +590,14 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::GetListItemIndex: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                step.functionReturnReg->value = getListItemIndex(listPtr, arg.second, func);
+                break;
+            }
+
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
                     freeHeap();
@@ -929,6 +937,13 @@ void LLVMCodeBuilder::addListItem(List *list)
     m_instructions.push_back(ins);
 }
 
+void LLVMCodeBuilder::addListItemIndex(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListItemIndex, Compiler::StaticType::Number, Compiler::StaticType::Unknown, 1);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::addListSize(List *list)
 {
     LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListSize, Compiler::StaticType::Number, {}, 0);
@@ -1752,6 +1767,56 @@ llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Valu
     return m_builder.CreateGEP(m_valueDataType, m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
 }
 
+llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegisterPtr item, llvm::Function *func)
+{
+    llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+    llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+    llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+    llvm::BasicBlock *cmpIfBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+    llvm::BasicBlock *cmpElseBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+    llvm::BasicBlock *notFoundBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+    llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+
+    // index = 0
+    llvm::Value *index = m_builder.CreateAlloca(m_builder.getInt64Ty());
+    m_builder.CreateStore(m_builder.getInt64(0), index);
+    m_builder.CreateBr(condBlock);
+
+    // while (index < size)
+    m_builder.SetInsertPoint(condBlock);
+    llvm::Value *cond = m_builder.CreateICmpULT(m_builder.CreateLoad(m_builder.getInt64Ty(), index), size);
+    m_builder.CreateCondBr(cond, bodyBlock, notFoundBlock);
+
+    // if (list[index] == item)
+    m_builder.SetInsertPoint(bodyBlock);
+    LLVMRegisterPtr currentItem = std::make_shared<LLVMRegister>(listPtr.type);
+    currentItem->isRawValue = false;
+    currentItem->value = getListItem(listPtr, m_builder.CreateLoad(m_builder.getInt64Ty(), index), func);
+    llvm::Value *cmp = createComparison(currentItem, item, Comparison::EQ);
+    m_builder.CreateCondBr(cmp, cmpIfBlock, cmpElseBlock);
+
+    // goto nextBlock
+    m_builder.SetInsertPoint(cmpIfBlock);
+    m_builder.CreateBr(nextBlock);
+
+    // else index++
+    m_builder.SetInsertPoint(cmpElseBlock);
+    m_builder.CreateStore(m_builder.CreateAdd(m_builder.CreateLoad(m_builder.getInt64Ty(), index), m_builder.getInt64(1)), index);
+    m_builder.CreateBr(condBlock);
+
+    // notFoundBlock:
+    // index = -1
+    // goto nextBlock
+    m_builder.SetInsertPoint(notFoundBlock);
+    m_builder.CreateStore(llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_ctx), -1, true), index);
+    m_builder.CreateBr(nextBlock);
+
+    // nextBlock:
+    m_builder.SetInsertPoint(nextBlock);
+
+    return m_builder.CreateSIToFP(m_builder.CreateLoad(m_builder.getInt64Ty(), index), m_builder.getDoubleTy());
+}
+
 llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
 {
     if (reg->isConstValue) {
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index a83affa9..d00a585d 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -30,6 +30,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void addVariableValue(Variable *variable) override;
         void addListContents(List *list) override;
         void addListItem(List *list) override;
+        void addListItemIndex(List *list) override;
         void addListSize(List *list) override;
 
         void createAdd() override;
@@ -123,6 +124,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
         llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func);
+        llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegisterPtr item, llvm::Function *func);
         llvm::Value *createValue(LLVMRegisterPtr reg);
         llvm::Value *createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type);
 
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 70bf7776..2aaedd13 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -49,6 +49,7 @@ struct LLVMInstruction
             ListReplace,
             GetListItem,
             GetListSize,
+            GetListItemIndex,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index a933298b..3ba93ea6 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2304,6 +2304,97 @@ TEST_F(LLVMCodeBuilderTest, GetListSize)
     ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
 }
 
+TEST_F(LLVMCodeBuilderTest, GetListItemIndex)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(2);
+    m_builder->addListItemIndex(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addListItemIndex(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(0);
+    m_builder->addListItemIndex(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get());
+
+    m_builder->addConstValue(2);
+    m_builder->addListItemIndex(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addListItemIndex(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("test");
+    m_builder->addListItemIndex(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("abc");
+    m_builder->addListItemIndex(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("doLor");
+    m_builder->addListItemIndex(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(true);
+    m_builder->addListItemIndex(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("site");
+    m_builder->addListItemIndex(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    static const std::string expected =
+        "1\n"
+        "0\n"
+        "-1\n"
+        "-1\n"
+        "0\n"
+        "1\n"
+        "-1\n"
+        "2\n"
+        "-1\n"
+        "-1\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+
+    ASSERT_EQ(globalList->toString(), "1 test 3");
+    ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 9d312010..1903807f 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -14,6 +14,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, addVariableValue, (Variable *), (override));
         MOCK_METHOD(void, addListContents, (List *), (override));
         MOCK_METHOD(void, addListItem, (List *), (override));
+        MOCK_METHOD(void, addListItemIndex, (List *), (override));
         MOCK_METHOD(void, addListSize, (List *), (override));
 
         MOCK_METHOD(void, createAdd, (), (override));

From c40db7fe482fcd200d8354283749b84aafad6a67 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Nov 2024 14:31:19 +0100
Subject: [PATCH 074/259] LLVMCodeBuilder: Uncomment list replace test

---
 test/dev/llvm/llvmcodebuilder_test.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 3ba93ea6..b22d39b3 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2134,7 +2134,7 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
     ASSERT_EQ(localList->toString(), "false hello world true");
 }
 
-/*TEST_F(LLVMCodeBuilderTest, ListReplace)
+TEST_F(LLVMCodeBuilderTest, ListReplace)
 {
     EngineMock engine;
     Stage stage;
@@ -2188,7 +2188,7 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
 
     ASSERT_EQ(globalList->toString(), "1 test 1");
     ASSERT_EQ(localList->toString(), "3 ipsum true hello world");
-}*/
+}
 
 TEST_F(LLVMCodeBuilderTest, GetListItem)
 {

From b0d26666df25fbca2accc0b217f9005c88d43d53 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Nov 2024 14:32:50 +0100
Subject: [PATCH 075/259] LLVMCodeBuilder: Implement list contains

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 20 +++-
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 91 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 113 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 4d602122..30d60cfe 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -25,6 +25,7 @@ class ICodeBuilder
         virtual void addListContents(List *list) = 0;
         virtual void addListItem(List *list) = 0;
         virtual void addListItemIndex(List *list) = 0;
+        virtual void addListContains(List *list) = 0;
         virtual void addListSize(List *list) = 0;
 
         virtual void createAdd() = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 8cc39abf..cd791169 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -594,7 +594,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                step.functionReturnReg->value = getListItemIndex(listPtr, arg.second, func);
+                step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second, func), m_builder.getDoubleTy());
+                break;
+            }
+
+            case LLVMInstruction::Type::ListContainsItem: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *index = getListItemIndex(listPtr, arg.second, func);
+                step.functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true));
                 break;
             }
 
@@ -944,6 +953,13 @@ void LLVMCodeBuilder::addListItemIndex(List *list)
     m_listPtrs[list] = LLVMListPtr();
 }
 
+void LLVMCodeBuilder::addListContains(List *list)
+{
+    LLVMInstruction &ins = createOp(LLVMInstruction::Type::ListContainsItem, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, 1);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+}
+
 void LLVMCodeBuilder::addListSize(List *list)
 {
     LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListSize, Compiler::StaticType::Number, {}, 0);
@@ -1814,7 +1830,7 @@ llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMR
     // nextBlock:
     m_builder.SetInsertPoint(nextBlock);
 
-    return m_builder.CreateSIToFP(m_builder.CreateLoad(m_builder.getInt64Ty(), index), m_builder.getDoubleTy());
+    return m_builder.CreateLoad(m_builder.getInt64Ty(), index);
 }
 
 llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index d00a585d..ef52b110 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -31,6 +31,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void addListContents(List *list) override;
         void addListItem(List *list) override;
         void addListItemIndex(List *list) override;
+        void addListContains(List *list) override;
         void addListSize(List *list) override;
 
         void createAdd() override;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 2aaedd13..afc13d1d 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -50,6 +50,7 @@ struct LLVMInstruction
             GetListItem,
             GetListSize,
             GetListItemIndex,
+            ListContainsItem,
             Yield,
             BeginIf,
             BeginElse,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index b22d39b3..268e5802 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2395,6 +2395,97 @@ TEST_F(LLVMCodeBuilderTest, GetListItemIndex)
     ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
 }
 
+TEST_F(LLVMCodeBuilderTest, ListContainsItem)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+
+    createBuilder(&sprite, true);
+
+    m_builder->addConstValue(2);
+    m_builder->addListContains(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addListContains(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(0);
+    m_builder->addListContains(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get());
+
+    m_builder->addConstValue(2);
+    m_builder->addListContains(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addListContains(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("test");
+    m_builder->addListContains(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("abc");
+    m_builder->addListContains(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("doLor");
+    m_builder->addListContains(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(true);
+    m_builder->addListContains(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue("site");
+    m_builder->addListContains(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    static const std::string expected =
+        "true\n"
+        "true\n"
+        "false\n"
+        "false\n"
+        "true\n"
+        "true\n"
+        "false\n"
+        "true\n"
+        "false\n"
+        "false\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+
+    ASSERT_EQ(globalList->toString(), "1 test 3");
+    ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
+}
+
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 1903807f..4aa0daeb 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -15,6 +15,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, addListContents, (List *), (override));
         MOCK_METHOD(void, addListItem, (List *), (override));
         MOCK_METHOD(void, addListItemIndex, (List *), (override));
+        MOCK_METHOD(void, addListContains, (List *), (override));
         MOCK_METHOD(void, addListSize, (List *), (override));
 
         MOCK_METHOD(void, createAdd, (), (override));

From d07178386c255da43bffc1068071cfff37880a8a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 21 Nov 2024 16:25:57 +0100
Subject: [PATCH 076/259] Compiler: Add list methods

---
 include/scratchcpp/dev/compiler.h   | 10 +++++
 src/dev/engine/compiler.cpp         | 57 +++++++++++++++++++++++++
 test/dev/compiler/compiler_test.cpp | 64 +++++++++++++++++++++++++++++
 3 files changed, 131 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index fa6550c8..b42f976d 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -46,6 +46,10 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void addConstValue(const Value &value);
         void addVariableValue(Variable *variable);
         void addListContents(List *list);
+        void addListItem(List *list);
+        void addListItemIndex(List *list);
+        void addListContains(List *list);
+        void addListSize(List *list);
         void addInput(const std::string &name);
 
         void createAdd();
@@ -80,6 +84,12 @@ class LIBSCRATCHCPP_EXPORT Compiler
 
         void createVariableWrite(Variable *variable);
 
+        void createListClear(List *list);
+        void createListRemove(List *list);
+        void createListAppend(List *list);
+        void createListInsert(List *list);
+        void createListReplace(List *list);
+
         void beginIfStatement();
         void beginElseBranch();
         void endIf();
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index ed225ff2..c8f134fa 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -100,6 +100,30 @@ void Compiler::addListContents(List *list)
     impl->builder->addListContents(list);
 }
 
+/*! Adds the item with index from the last value of the given list to the code. */
+void Compiler::addListItem(List *list)
+{
+    impl->builder->addListItem(list);
+}
+
+/*! Adds the index of the item from the last value of the given list to the code. */
+void Compiler::addListItemIndex(List *list)
+{
+    impl->builder->addListItemIndex(list);
+}
+
+/*! Adds the result of a list contains item from the  check to the code. */
+void Compiler::addListContains(List *list)
+{
+    impl->builder->addListContains(list);
+}
+
+/*! Adds the length of the given list to the code. */
+void Compiler::addListSize(List *list)
+{
+    impl->builder->addListSize(list);
+}
+
 /*! Compiles the given input (resolved by name) and adds it to the compiled code. */
 void Compiler::addInput(const std::string &name)
 {
@@ -268,6 +292,39 @@ void Compiler::createVariableWrite(Variable *variable)
     impl->builder->createVariableWrite(variable);
 }
 
+/*! Creates a clear list operation. */
+void Compiler::createListClear(List *list)
+{
+    impl->builder->createListClear(list);
+}
+
+/*!
+ * Creates a remove item from list operation.
+ * \note The index starts with 0 and is cast to number, special strings like "last" are not handled.
+ */
+void Compiler::createListRemove(List *list)
+{
+    impl->builder->createListRemove(list);
+}
+
+/*! Creates a list append operation using the last value. */
+void Compiler::createListAppend(List *list)
+{
+    impl->builder->createListAppend(list);
+}
+
+/*! Creates a list insert operation using the last 2 values (index, value). */
+void Compiler::createListInsert(List *list)
+{
+    impl->builder->createListInsert(list);
+}
+
+/*! Creates a list replace operation using the last 2 values (index, value). */
+void Compiler::createListReplace(List *list)
+{
+    impl->builder->createListReplace(list);
+}
+
 /*!
  * Starts a custom if statement.
  * \note The if statement must be terminated using endIf() after compiling your block.
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 822eb358..9042a515 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -132,6 +132,70 @@ TEST_F(CompilerTest, AddListContents)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, AddListItem)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) {
+        List list1("", ""), list2("", "");
+        EXPECT_CALL(*m_builder, addListItem(&list1));
+        compiler->addListItem(&list1);
+
+        EXPECT_CALL(*m_builder, addListItem(&list2));
+        compiler->addListItem(&list2);
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, AddListItemIndex)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) {
+        List list1("", ""), list2("", "");
+        EXPECT_CALL(*m_builder, addListItemIndex(&list1));
+        compiler->addListItemIndex(&list1);
+
+        EXPECT_CALL(*m_builder, addListItemIndex(&list2));
+        compiler->addListItemIndex(&list2);
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, AddListSize)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) {
+        List list1("", ""), list2("", "");
+        EXPECT_CALL(*m_builder, addListSize(&list1));
+        compiler->addListSize(&list1);
+
+        EXPECT_CALL(*m_builder, addListSize(&list2));
+        compiler->addListSize(&list2);
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, AddListContains)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) {
+        List list1("", ""), list2("", "");
+        EXPECT_CALL(*m_builder, addListContains(&list1));
+        compiler->addListContains(&list1);
+
+        EXPECT_CALL(*m_builder, addListContains(&list2));
+        compiler->addListContains(&list2);
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, AddInput)
 {
     Compiler compiler(&m_engine, &m_target);

From ebef1af9246d3f11da0e72d8b662ffe08d8c17e8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Nov 2024 00:16:29 +0100
Subject: [PATCH 077/259] LLVMCodeBuilder: Fix dataPtrDirty in list insert

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index cd791169..93e39239 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -547,9 +547,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
                 // dataPtrDirty
+                llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
                 llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
-                m_builder.CreateStore(m_builder.CreateICmpEQ(allocatedSize, size), listPtr.dataPtrDirty);
+                m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpEQ(allocatedSize, size)), listPtr.dataPtrDirty);
 
                 // Insert
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());

From 077bb7b55fd709ffbe7931ff2f22b71cd26a905b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Nov 2024 00:18:43 +0100
Subject: [PATCH 078/259] LLVMCodeBuilder: Rewrite createInitialValueStore()

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 21 +++++++------------
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +-
 2 files changed, 9 insertions(+), 14 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 93e39239..94819db2 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -523,14 +523,14 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // If there's enough space, use the allocated memory
                 m_builder.SetInsertPoint(ifBlock);
                 llvm::Value *itemPtr = getListItem(listPtr, size, func);
-                createInitialValueStore(arg.second, itemPtr, type);
+                createReusedValueStore(arg.second, itemPtr, type);
                 m_builder.CreateStore(m_builder.CreateAdd(size, m_builder.getInt64(1)), listPtr.sizePtr);
                 m_builder.CreateBr(nextBlock);
 
                 // Otherwise call appendEmpty()
                 m_builder.SetInsertPoint(elseBlock);
                 itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr);
-                createInitialValueStore(arg.second, itemPtr, type);
+                createReusedValueStore(arg.second, itemPtr, type);
                 m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty);
                 m_builder.CreateBr(nextBlock);
 
@@ -555,8 +555,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // Insert
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
                 llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
-                createInitialValueStore(valueArg.second, itemPtr, type);
                 // TODO: Implement list type prediction
+                createReusedValueStore(valueArg.second, itemPtr, type);
                 break;
             }
 
@@ -1727,25 +1727,20 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP
     }
 }
 
-void LLVMCodeBuilder::createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType)
+void LLVMCodeBuilder::createReusedValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType)
 {
     llvm::Value *converted = nullptr;
 
     if (sourceType != Compiler::StaticType::Unknown)
         converted = castValue(reg, sourceType);
 
-    auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair<ValueType, Compiler::StaticType> &pair) { return pair.second == sourceType; });
-    const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored
-
-    llvm::Value *valuePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0);
-    llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1);
-    m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typePtr);
-
     switch (sourceType) {
         case Compiler::StaticType::Number:
+            m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted });
+            break;
+
         case Compiler::StaticType::Bool:
-            // Write number/bool directly
-            m_builder.CreateStore(converted, valuePtr);
+            m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted });
             break;
 
         case Compiler::StaticType::String:
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index ef52b110..c9266a09 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -121,7 +121,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector<Compiler::StaticType> &argTypes, size_t argCount);
 
         void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
-        void createInitialValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
+        void createReusedValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
         llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func);

From 7f62c8b4b1db59f61c686fd56697952a48d09d2a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Nov 2024 00:24:13 +0100
Subject: [PATCH 079/259] LLVMCodeBuilder: Fix dataPrtDirty in list clear

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 94819db2..700d9ee9 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -489,9 +489,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
             case LLVMInstruction::Type::ClearList: {
                 assert(step.args.size() == 0);
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 m_builder.CreateCall(resolve_list_clear(), listPtr.ptr);
-                // NOTE: Clearing doesn't deallocate (see List::clear()), so there's no need to update the data pointer
+                m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty);
+
+                // Clearing may deallocate, so check if the allocated size changed
+                llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
+                llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
+                m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpNE(allocatedSize, oldAllocatedSize)), listPtr.dataPtrDirty);
                 break;
             }
 

From 2219f8b10aa7e7c2aa96696aa98598664ae3363e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Nov 2024 00:24:48 +0100
Subject: [PATCH 080/259] LLVMCodeBuilder: Implement list type prediction in
 scripts

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  82 +++-
 .../engine/internal/llvm/llvmcodebuilder.h    |   1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 456 ++++++++++++++++++
 3 files changed, 526 insertions(+), 13 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 700d9ee9..d6943ba6 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -92,9 +92,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
         varPtr.onStack = false; // use heap before the first assignment
     }
 
-    m_scopeVariables.clear();
-    m_scopeVariables.push_back({});
-
     // Create list pointers
     for (auto &[list, listPtr] : m_listPtrs) {
         listPtr.ptr = getListPtr(targetLists, list);
@@ -109,6 +106,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
         m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
     }
 
+    m_scopeVariables.clear();
+    m_scopeLists.clear();
+    pushScopeLevel();
+
     // Execute recorded steps
     for (const LLVMInstruction &step : m_instructions) {
         switch (step.type) {
@@ -498,6 +499,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
                 llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpNE(allocatedSize, oldAllocatedSize)), listPtr.dataPtrDirty);
+
+                m_scopeLists.back().erase(&listPtr);
                 break;
             }
 
@@ -515,7 +518,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 Compiler::StaticType type = optimizeRegisterType(arg.second);
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
                 // Check if enough space is allocated
                 llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
@@ -541,7 +544,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.CreateBr(nextBlock);
 
                 m_builder.SetInsertPoint(nextBlock);
-                // TODO: Implement list type prediction
+                auto &typeMap = m_scopeLists.back();
+
+                if (typeMap.find(&listPtr) == typeMap.cend()) {
+                    listPtr.type = type;
+                    typeMap[&listPtr] = listPtr.type;
+                } else if (listPtr.type != type) {
+                    listPtr.type = Compiler::StaticType::Unknown;
+                    typeMap[&listPtr] = listPtr.type;
+                }
+
                 break;
             }
 
@@ -550,7 +562,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &indexArg = step.args[0];
                 const auto &valueArg = step.args[1];
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
                 // dataPtrDirty
                 llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
@@ -561,8 +573,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // Insert
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
                 llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
-                // TODO: Implement list type prediction
                 createReusedValueStore(valueArg.second, itemPtr, type);
+
+                auto &typeMap = m_scopeLists.back();
+
+                if (typeMap.find(&listPtr) == typeMap.cend()) {
+                    listPtr.type = type;
+                    typeMap[&listPtr] = listPtr.type;
+                } else if (listPtr.type != type) {
+                    listPtr.type = Compiler::StaticType::Unknown;
+                    typeMap[&listPtr] = listPtr.type;
+                }
+
                 break;
             }
 
@@ -571,11 +593,21 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &indexArg = step.args[0];
                 const auto &valueArg = step.args[1];
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
                 llvm::Value *itemPtr = getListItem(listPtr, index, func);
                 createValueStore(valueArg.second, itemPtr, type, listPtr.type);
-                // TODO: Implement list type prediction
+
+                auto &typeMap = m_scopeLists.back();
+
+                if (typeMap.find(&listPtr) == typeMap.cend()) {
+                    listPtr.type = type;
+                    typeMap[&listPtr] = listPtr.type;
+                } else if (listPtr.type != type) {
+                    listPtr.type = Compiler::StaticType::Unknown;
+                    typeMap[&listPtr] = listPtr.type;
+                }
+
                 break;
             }
 
@@ -618,9 +650,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 if (!m_warp) {
                     freeHeap();
                     syncVariables(targetVariables);
-                    reloadLists();
                     coro->createSuspend();
                     reloadVariables(targetVariables);
+                    reloadLists();
                 }
 
                 break;
@@ -1285,6 +1317,16 @@ void LLVMCodeBuilder::createListMap()
 void LLVMCodeBuilder::pushScopeLevel()
 {
     m_scopeVariables.push_back({});
+
+    if (m_scopeLists.empty()) {
+        std::unordered_map<LLVMListPtr *, Compiler::StaticType> listTypes;
+
+        for (auto &[list, listPtr] : m_listPtrs)
+            listTypes[&listPtr] = Compiler::StaticType::Unknown;
+
+        m_scopeLists.push_back(listTypes);
+    } else
+        m_scopeLists.push_back(m_scopeLists.back());
 }
 
 void LLVMCodeBuilder::popScopeLevel()
@@ -1297,6 +1339,15 @@ void LLVMCodeBuilder::popScopeLevel()
     }
 
     m_scopeVariables.pop_back();
+
+    for (size_t i = 0; i < m_scopeLists.size() - 1; i++) {
+        for (auto &[ptr, type] : m_scopeLists[i]) {
+            if (ptr->type != type)
+                ptr->type = Compiler::StaticType::Unknown;
+        }
+    }
+
+    m_scopeLists.pop_back();
 }
 
 void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
@@ -1605,9 +1656,14 @@ void LLVMCodeBuilder::reloadVariables(llvm::Value *targetVariables)
 
 void LLVMCodeBuilder::reloadLists()
 {
-    // Reload list data pointers
-    for (auto &[list, listPtr] : m_listPtrs)
-        m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr);
+    // Reset list data dirty and list types
+    auto &typeMap = m_scopeLists.back();
+
+    for (auto &[list, listPtr] : m_listPtrs) {
+        m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty);
+        listPtr.type = Compiler::StaticType::Unknown;
+        typeMap[&listPtr] = listPtr.type;
+    }
 }
 
 void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func)
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index c9266a09..fa296136 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -165,6 +165,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         std::unordered_map<List *, size_t> m_targetListMap;
         std::unordered_map<List *, LLVMListPtr> m_listPtrs;
+        std::vector<std::unordered_map<LLVMListPtr *, Compiler::StaticType>> m_scopeLists;
 
         std::string m_id;
         llvm::LLVMContext m_ctx;
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 268e5802..747a63f5 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2607,6 +2607,119 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList1 = std::make_shared<List>("", "");
+    stage.addList(globalList1);
+
+    auto globalList2 = std::make_shared<List>("", "");
+    stage.addList(globalList2);
+
+    auto localList1 = std::make_shared<List>("", "");
+    sprite.addList(localList1);
+
+    auto localList2 = std::make_shared<List>("", "");
+    sprite.addList(localList2);
+
+    createBuilder(&sprite, false);
+
+    m_builder->createListClear(globalList1.get());
+    m_builder->createListClear(globalList2.get());
+    m_builder->createListClear(localList1.get());
+    m_builder->createListClear(localList2.get());
+
+    m_builder->addConstValue(1);
+    m_builder->createListAppend(globalList1.get());
+    m_builder->addConstValue(2);
+    m_builder->createListAppend(globalList1.get());
+    m_builder->addConstValue(3);
+    m_builder->createListAppend(globalList1.get());
+
+    m_builder->addConstValue(1);
+    m_builder->createListAppend(globalList2.get());
+    m_builder->addConstValue(2);
+    m_builder->createListAppend(globalList2.get());
+    m_builder->addConstValue(3);
+    m_builder->createListAppend(globalList2.get());
+
+    m_builder->addConstValue(1);
+    m_builder->addConstValue(12.5);
+    m_builder->createListReplace(globalList2.get());
+
+    m_builder->addConstValue(0);
+    m_builder->addConstValue("Lorem");
+    m_builder->createListInsert(localList1.get());
+    m_builder->addConstValue(0);
+    m_builder->addConstValue("ipsum");
+    m_builder->createListInsert(localList1.get());
+
+    m_builder->addConstValue(0);
+    m_builder->addConstValue(true);
+    m_builder->createListInsert(localList2.get());
+    m_builder->addConstValue(0);
+    m_builder->addConstValue(false);
+    m_builder->createListInsert(localList2.get());
+
+    m_builder->yield();
+
+    m_builder->addConstValue(1);
+    m_builder->addListItem(globalList1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(2);
+    m_builder->addListItem(globalList1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addListItem(globalList2.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(0);
+    m_builder->addListItem(globalList2.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(0);
+    m_builder->addListItem(localList1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(1);
+    m_builder->addListItem(localList1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    m_builder->addConstValue(0);
+    m_builder->addListItem(localList2.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+    std::string expected =
+        "2\n"
+        "test\n"
+        "12.5\n"
+        "false\n"
+        "ipsum\n"
+        "-5.48\n"
+        "hello\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    globalList1->replace(2, "test");
+    globalList2->replace(0, false);
+    localList1->replace(1, -5.48);
+    localList2->replace(0, "hello");
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, IfStatement)
 {
     createBuilder(true);
@@ -2872,6 +2985,151 @@ TEST_F(LLVMCodeBuilderTest, IfStatementVariables)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, IfStatementLists)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList1 = std::make_shared<List>("", "");
+    stage.addList(globalList1);
+
+    auto globalList2 = std::make_shared<List>("", "");
+    stage.addList(globalList2);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, false);
+
+    auto resetLists = [this, globalList1, globalList2, localList]() {
+        m_builder->createListClear(globalList1.get());
+        m_builder->createListClear(globalList2.get());
+        m_builder->createListClear(localList.get());
+
+        m_builder->addConstValue(1);
+        m_builder->createListAppend(globalList1.get());
+        m_builder->addConstValue(2);
+        m_builder->createListAppend(globalList1.get());
+
+        m_builder->addConstValue("hello");
+        m_builder->createListAppend(globalList2.get());
+        m_builder->addConstValue("world");
+        m_builder->createListAppend(globalList2.get());
+
+        m_builder->addConstValue(false);
+        m_builder->createListAppend(localList.get());
+        m_builder->addConstValue(true);
+        m_builder->createListAppend(localList.get());
+    };
+
+    auto checkLists = [this, globalList1, globalList2, localList]() {
+        m_builder->addConstValue(0);
+        m_builder->addListItem(globalList1.get());
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+        m_builder->addConstValue(1);
+        m_builder->addListItem(globalList2.get());
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+        m_builder->addConstValue(0);
+        m_builder->addListItem(localList.get());
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    };
+
+    // if (true)
+    resetLists();
+
+    m_builder->addConstValue(true);
+    m_builder->beginIfStatement();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get());
+
+        m_builder->addConstValue(0);
+        m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get());
+
+        m_builder->addConstValue(1);
+        m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get());
+    }
+    m_builder->endIf();
+
+    checkLists();
+
+    // if (false)
+    resetLists();
+
+    m_builder->addConstValue(false);
+    m_builder->beginIfStatement();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get());
+
+        m_builder->addConstValue(0);
+        m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get());
+
+        m_builder->addConstValue(1);
+        m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get());
+    }
+    m_builder->endIf();
+
+    checkLists();
+
+    // if (true) { if (true) { ... }; if (false) { ... } }
+    resetLists();
+
+    m_builder->addConstValue(true);
+    m_builder->beginIfStatement();
+    {
+        m_builder->addConstValue(true);
+        m_builder->beginIfStatement();
+        {
+            m_builder->addConstValue("hello world");
+            m_builder->createListAppend(globalList1.get());
+
+            m_builder->addConstValue(0);
+            m_builder->addConstValue(8.5);
+            m_builder->createListInsert(globalList2.get());
+        }
+        m_builder->endIf();
+
+        m_builder->addConstValue(false);
+        m_builder->beginIfStatement();
+        {
+            m_builder->addConstValue(1);
+            m_builder->addConstValue(-4.25);
+            m_builder->createListReplace(localList.get());
+        }
+        m_builder->endIf();
+    }
+    m_builder->endIf();
+
+    checkLists();
+
+    std::string expected =
+        "1\n"
+        "hello\n"
+        "false\n"
+        "1\n"
+        "world\n"
+        "false\n"
+        "1\n"
+        "hello\n"
+        "false\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, RepeatLoop)
 {
     createBuilder(true);
@@ -3344,3 +3602,201 @@ TEST_F(LLVMCodeBuilderTest, LoopVariables)
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
+
+TEST_F(LLVMCodeBuilderTest, LoopLists)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList1 = std::make_shared<List>("", "");
+    stage.addList(globalList1);
+
+    auto globalList2 = std::make_shared<List>("", "");
+    stage.addList(globalList2);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    auto counter1 = std::make_shared<Variable>("", "");
+    auto counter2 = std::make_shared<Variable>("", "");
+    sprite.addVariable(counter1);
+    sprite.addVariable(counter2);
+
+    createBuilder(&sprite, true);
+
+    auto resetLists = [this, globalList1, globalList2, localList]() {
+        m_builder->createListClear(globalList1.get());
+        m_builder->createListClear(globalList2.get());
+        m_builder->createListClear(localList.get());
+
+        m_builder->addConstValue(1);
+        m_builder->createListAppend(globalList1.get());
+        m_builder->addConstValue(2);
+        m_builder->createListAppend(globalList1.get());
+
+        m_builder->addConstValue("hello");
+        m_builder->createListAppend(globalList2.get());
+        m_builder->addConstValue("world");
+        m_builder->createListAppend(globalList2.get());
+
+        m_builder->addConstValue(false);
+        m_builder->createListAppend(localList.get());
+        m_builder->addConstValue(true);
+        m_builder->createListAppend(localList.get());
+    };
+
+    auto checkLists = [this, globalList1, globalList2, localList]() {
+        m_builder->addConstValue(0);
+        m_builder->addListItem(globalList1.get());
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+        m_builder->addConstValue(1);
+        m_builder->addListItem(globalList2.get());
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+
+        m_builder->addConstValue(0);
+        m_builder->addListItem(localList.get());
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    };
+
+    // repeat (2)
+    resetLists();
+
+    m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get());
+
+        m_builder->addConstValue(0);
+        m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get());
+
+        m_builder->addConstValue(1);
+        m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get());
+    }
+    m_builder->endLoop();
+
+    checkLists();
+
+    // repeat(0)
+    resetLists();
+
+    m_builder->addConstValue(0);
+    m_builder->beginRepeatLoop();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get());
+
+        m_builder->addConstValue(0);
+        m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get());
+
+        m_builder->addConstValue(1);
+        m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get());
+    }
+    m_builder->endLoop();
+
+    checkLists();
+
+    // while (counter1 < 5) { ... until (counter2 == 3) {... counter2++ }; while (false) { ... }; counter1++ }
+    resetLists();
+
+    m_builder->addConstValue(0);
+    m_builder->createVariableWrite(counter1.get());
+
+    m_builder->beginLoopCondition();
+    m_builder->addVariableValue(counter1.get());
+    m_builder->addConstValue(5);
+    m_builder->createCmpLT();
+    m_builder->beginWhileLoop();
+    {
+        m_builder->addConstValue(0);
+        m_builder->createVariableWrite(counter2.get());
+
+        m_builder->beginLoopCondition();
+        m_builder->addVariableValue(counter2.get());
+        m_builder->addConstValue(3);
+        m_builder->createCmpEQ();
+        m_builder->beginRepeatUntilLoop();
+        {
+            m_builder->addConstValue(0);
+            m_builder->addConstValue(8.5);
+            m_builder->createListInsert(globalList2.get());
+
+            m_builder->addConstValue(1);
+            m_builder->addConstValue(-4.25);
+            m_builder->createListReplace(localList.get());
+
+            m_builder->addVariableValue(counter2.get());
+            m_builder->addConstValue(1);
+            m_builder->createAdd();
+            m_builder->createVariableWrite(counter2.get());
+        }
+        m_builder->endLoop();
+
+        m_builder->beginLoopCondition();
+        m_builder->addConstValue(false);
+        m_builder->beginWhileLoop();
+        {
+            m_builder->addConstValue("hello world");
+            m_builder->createListAppend(globalList1.get());
+        }
+        m_builder->endLoop();
+
+        m_builder->addVariableValue(counter1.get());
+        m_builder->addConstValue(1);
+        m_builder->createAdd();
+        m_builder->createVariableWrite(counter1.get());
+    }
+    m_builder->endLoop();
+
+    checkLists();
+
+    // until (true)
+    resetLists();
+
+    m_builder->beginLoopCondition();
+    m_builder->addConstValue(true);
+    m_builder->beginRepeatUntilLoop();
+    {
+        m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get());
+
+        m_builder->addConstValue(0);
+        m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get());
+
+        m_builder->addConstValue(1);
+        m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get());
+    }
+    m_builder->endLoop();
+
+    checkLists();
+
+    std::string expected =
+        "1\n"
+        "8.5\n"
+        "false\n"
+        "1\n"
+        "world\n"
+        "false\n"
+        "1\n"
+        "8.5\n"
+        "false\n"
+        "1\n"
+        "world\n"
+        "false\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}

From ddced8660cfef7909016626e542d4ed47baff84f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Nov 2024 20:33:22 +0100
Subject: [PATCH 081/259] Add CompilerValue class

---
 CMakeLists.txt                           |  1 +
 include/scratchcpp/dev/compilervalue.h   | 29 ++++++++++++++++
 src/dev/engine/CMakeLists.txt            |  3 ++
 src/dev/engine/compilervalue.cpp         | 25 ++++++++++++++
 src/dev/engine/compilervalue_p.cpp       | 10 ++++++
 src/dev/engine/compilervalue_p.h         | 17 ++++++++++
 test/dev/compiler/CMakeLists.txt         |  1 +
 test/dev/compiler/compilervalue_test.cpp | 42 ++++++++++++++++++++++++
 8 files changed, 128 insertions(+)
 create mode 100644 include/scratchcpp/dev/compilervalue.h
 create mode 100644 src/dev/engine/compilervalue.cpp
 create mode 100644 src/dev/engine/compilervalue_p.cpp
 create mode 100644 src/dev/engine/compilervalue_p.h
 create mode 100644 test/dev/compiler/compilervalue_test.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5c9f64fe..b360e7b5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,6 +72,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
     target_sources(scratchcpp
       PUBLIC
         include/scratchcpp/dev/compiler.h
+        include/scratchcpp/dev/compilervalue.h
         include/scratchcpp/dev/executablecode.h
         include/scratchcpp/dev/executioncontext.h
     )
diff --git a/include/scratchcpp/dev/compilervalue.h b/include/scratchcpp/dev/compilervalue.h
new file mode 100644
index 00000000..c783d8ac
--- /dev/null
+++ b/include/scratchcpp/dev/compilervalue.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "compiler.h"
+
+namespace libscratchcpp
+{
+
+class CompilerValuePrivate;
+
+/*! \brief The CompilerValue class represents a local value in compiled code. */
+class LIBSCRATCHCPP_EXPORT CompilerValue
+{
+    public:
+        CompilerValue(Compiler::StaticType type);
+        CompilerValue(const CompilerValue &) = delete;
+        virtual ~CompilerValue() { }
+
+        Compiler::StaticType type() const;
+        void setType(Compiler::StaticType type);
+
+        virtual bool isConst() const { return false; };
+
+    private:
+        spimpl::unique_impl_ptr<CompilerValuePrivate> impl;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index 61d172da..a7c7ef83 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -3,6 +3,9 @@ target_sources(scratchcpp
     compiler.cpp
     compiler_p.cpp
     compiler_p.h
+    compilervalue.cpp
+    compilervalue_p.cpp
+    compilervalue_p.h
     executioncontext.cpp
     executioncontext_p.cpp
     executioncontext_p.h
diff --git a/src/dev/engine/compilervalue.cpp b/src/dev/engine/compilervalue.cpp
new file mode 100644
index 00000000..9a601321
--- /dev/null
+++ b/src/dev/engine/compilervalue.cpp
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/dev/compilervalue.h>
+
+#include "compilervalue_p.h"
+
+using namespace libscratchcpp;
+
+/*! Constructs CompilerValue. */
+CompilerValue::CompilerValue(Compiler::StaticType type) :
+    impl(spimpl::make_unique_impl<CompilerValuePrivate>(type))
+{
+}
+
+/*! Returns the type of this value. */
+Compiler::StaticType CompilerValue::type() const
+{
+    return impl->type;
+}
+
+/*! Sets the type of this value. */
+void CompilerValue::setType(Compiler::StaticType type)
+{
+    impl->type = type;
+}
diff --git a/src/dev/engine/compilervalue_p.cpp b/src/dev/engine/compilervalue_p.cpp
new file mode 100644
index 00000000..a526ae6e
--- /dev/null
+++ b/src/dev/engine/compilervalue_p.cpp
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "compilervalue_p.h"
+
+using namespace libscratchcpp;
+
+CompilerValuePrivate::CompilerValuePrivate(Compiler::StaticType type) :
+    type(type)
+{
+}
diff --git a/src/dev/engine/compilervalue_p.h b/src/dev/engine/compilervalue_p.h
new file mode 100644
index 00000000..99a9112e
--- /dev/null
+++ b/src/dev/engine/compilervalue_p.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/dev/compiler.h>
+
+namespace libscratchcpp
+{
+
+struct CompilerValuePrivate
+{
+        CompilerValuePrivate(Compiler::StaticType type);
+
+        Compiler::StaticType type = Compiler::StaticType::Unknown;
+};
+
+} // namespace libscratchcpp
diff --git a/test/dev/compiler/CMakeLists.txt b/test/dev/compiler/CMakeLists.txt
index 546ac5c6..9759fa7d 100644
--- a/test/dev/compiler/CMakeLists.txt
+++ b/test/dev/compiler/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_executable(
   compiler_test
   compiler_test.cpp
+  compilervalue_test.cpp
 )
 
 target_link_libraries(
diff --git a/test/dev/compiler/compilervalue_test.cpp b/test/dev/compiler/compilervalue_test.cpp
new file mode 100644
index 00000000..a5256ba1
--- /dev/null
+++ b/test/dev/compiler/compilervalue_test.cpp
@@ -0,0 +1,42 @@
+#include <scratchcpp/dev/compilervalue.h>
+#include <gtest/gtest.h>
+
+using namespace libscratchcpp;
+
+TEST(CompilerValueTest, Constructors)
+{
+    {
+        CompilerValue v(Compiler::StaticType::Number);
+        ASSERT_EQ(v.type(), Compiler::StaticType::Number);
+    }
+
+    {
+        CompilerValue v(Compiler::StaticType::String);
+        ASSERT_EQ(v.type(), Compiler::StaticType::String);
+    }
+
+    {
+        CompilerValue v(Compiler::StaticType::Unknown);
+        ASSERT_EQ(v.type(), Compiler::StaticType::Unknown);
+    }
+}
+
+TEST(CompilerValueTest, Type)
+{
+    CompilerValue v(Compiler::StaticType::Unknown);
+
+    v.setType(Compiler::StaticType::String);
+    ASSERT_EQ(v.type(), Compiler::StaticType::String);
+
+    v.setType(Compiler::StaticType::Bool);
+    ASSERT_EQ(v.type(), Compiler::StaticType::Bool);
+
+    v.setType(Compiler::StaticType::Unknown);
+    ASSERT_EQ(v.type(), Compiler::StaticType::Unknown);
+}
+
+TEST(CompilerValueTest, IsConst)
+{
+    CompilerValue v(Compiler::StaticType::Unknown);
+    ASSERT_FALSE(v.isConst());
+}

From 8fadba1bfc5d052925a5d83eed2bacb7d4305130 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Nov 2024 20:34:14 +0100
Subject: [PATCH 082/259] Add CompilerConstant class

---
 CMakeLists.txt                              |  1 +
 include/scratchcpp/dev/compilerconstant.h   | 27 +++++++++++++++++
 src/dev/engine/CMakeLists.txt               |  3 ++
 src/dev/engine/compilerconstant.cpp         | 20 +++++++++++++
 src/dev/engine/compilerconstant_p.cpp       |  8 ++++++
 src/dev/engine/compilerconstant_p.h         | 17 +++++++++++
 test/dev/compiler/CMakeLists.txt            |  1 +
 test/dev/compiler/compilerconstant_test.cpp | 32 +++++++++++++++++++++
 8 files changed, 109 insertions(+)
 create mode 100644 include/scratchcpp/dev/compilerconstant.h
 create mode 100644 src/dev/engine/compilerconstant.cpp
 create mode 100644 src/dev/engine/compilerconstant_p.cpp
 create mode 100644 src/dev/engine/compilerconstant_p.h
 create mode 100644 test/dev/compiler/compilerconstant_test.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b360e7b5..138f5540 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -73,6 +73,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
       PUBLIC
         include/scratchcpp/dev/compiler.h
         include/scratchcpp/dev/compilervalue.h
+        include/scratchcpp/dev/compilerconstant.h
         include/scratchcpp/dev/executablecode.h
         include/scratchcpp/dev/executioncontext.h
     )
diff --git a/include/scratchcpp/dev/compilerconstant.h b/include/scratchcpp/dev/compilerconstant.h
new file mode 100644
index 00000000..51d6347b
--- /dev/null
+++ b/include/scratchcpp/dev/compilerconstant.h
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "compilervalue.h"
+
+namespace libscratchcpp
+{
+
+class CompilerConstantPrivate;
+
+/*! \brief The CompilerConstant class represents a constant value in compiled code. */
+class LIBSCRATCHCPP_EXPORT CompilerConstant : public CompilerValue
+{
+    public:
+        CompilerConstant(Compiler::StaticType type, const Value &value);
+        CompilerConstant(const CompilerConstant &) = delete;
+
+        bool isConst() const override final { return true; };
+
+        const Value &value() const;
+
+    private:
+        spimpl::unique_impl_ptr<CompilerConstantPrivate> impl;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index a7c7ef83..a4933a98 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -6,6 +6,9 @@ target_sources(scratchcpp
     compilervalue.cpp
     compilervalue_p.cpp
     compilervalue_p.h
+    compilerconstant.cpp
+    compilerconstant_p.cpp
+    compilerconstant_p.h
     executioncontext.cpp
     executioncontext_p.cpp
     executioncontext_p.h
diff --git a/src/dev/engine/compilerconstant.cpp b/src/dev/engine/compilerconstant.cpp
new file mode 100644
index 00000000..fb7dcf3b
--- /dev/null
+++ b/src/dev/engine/compilerconstant.cpp
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/dev/compilerconstant.h>
+
+#include "compilerconstant_p.h"
+
+using namespace libscratchcpp;
+
+/*! Constructs CompilerConstant. */
+CompilerConstant::CompilerConstant(Compiler::StaticType type, const Value &value) :
+    CompilerValue(type),
+    impl(spimpl::make_unique_impl<CompilerConstantPrivate>(value))
+{
+}
+
+/*! Returns the constant value. */
+const Value &CompilerConstant::value() const
+{
+    return impl->value;
+}
diff --git a/src/dev/engine/compilerconstant_p.cpp b/src/dev/engine/compilerconstant_p.cpp
new file mode 100644
index 00000000..c1e0b07e
--- /dev/null
+++ b/src/dev/engine/compilerconstant_p.cpp
@@ -0,0 +1,8 @@
+#include "compilerconstant_p.h"
+
+using namespace libscratchcpp;
+
+CompilerConstantPrivate::CompilerConstantPrivate(const Value &value) :
+    value(value)
+{
+}
diff --git a/src/dev/engine/compilerconstant_p.h b/src/dev/engine/compilerconstant_p.h
new file mode 100644
index 00000000..c7b983ef
--- /dev/null
+++ b/src/dev/engine/compilerconstant_p.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/value.h>
+
+namespace libscratchcpp
+{
+
+struct CompilerConstantPrivate
+{
+        CompilerConstantPrivate(const Value &value);
+
+        Value value;
+};
+
+} // namespace libscratchcpp
diff --git a/test/dev/compiler/CMakeLists.txt b/test/dev/compiler/CMakeLists.txt
index 9759fa7d..feee72ca 100644
--- a/test/dev/compiler/CMakeLists.txt
+++ b/test/dev/compiler/CMakeLists.txt
@@ -2,6 +2,7 @@ add_executable(
   compiler_test
   compiler_test.cpp
   compilervalue_test.cpp
+  compilerconstant_test.cpp
 )
 
 target_link_libraries(
diff --git a/test/dev/compiler/compilerconstant_test.cpp b/test/dev/compiler/compilerconstant_test.cpp
new file mode 100644
index 00000000..c7c58720
--- /dev/null
+++ b/test/dev/compiler/compilerconstant_test.cpp
@@ -0,0 +1,32 @@
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/value.h>
+#include <gtest/gtest.h>
+
+using namespace libscratchcpp;
+
+TEST(CompilerConstantTest, Constructors)
+{
+    {
+        CompilerConstant v(Compiler::StaticType::Number, 5);
+        ASSERT_EQ(v.type(), Compiler::StaticType::Number);
+        ASSERT_EQ(v.value(), 5);
+    }
+
+    {
+        CompilerConstant v(Compiler::StaticType::String, "test");
+        ASSERT_EQ(v.type(), Compiler::StaticType::String);
+        ASSERT_EQ(v.value(), "test");
+    }
+
+    {
+        CompilerConstant v(Compiler::StaticType::Unknown, true);
+        ASSERT_EQ(v.type(), Compiler::StaticType::Unknown);
+        ASSERT_EQ(v.value(), true);
+    }
+}
+
+TEST(CompilerConstantTest, IsConst)
+{
+    CompilerConstant v(Compiler::StaticType::Unknown, Value());
+    ASSERT_TRUE(v.isConst());
+}

From 076aadff48ef23de936d7429bfdccd96b4e98868 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 23 Nov 2024 20:35:37 +0100
Subject: [PATCH 083/259] Refactor new compiler to use CompilerValue

---
 include/scratchcpp/block.h                    |    9 +-
 include/scratchcpp/dev/compiler.h             |  109 +-
 include/scratchcpp/global.h                   |   21 +
 include/scratchcpp/inputvalue.h               |    7 +
 src/dev/engine/compiler.cpp                   |  290 +-
 src/dev/engine/compiler_p.h                   |    1 +
 src/dev/engine/internal/icodebuilder.h        |   96 +-
 src/dev/engine/internal/llvm/CMakeLists.txt   |    2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  366 +-
 .../engine/internal/llvm/llvmcodebuilder.h    |  124 +-
 .../internal/llvm/llvmconstantregister.h      |   25 +
 .../engine/internal/llvm/llvminstruction.h    |    4 +-
 src/dev/engine/internal/llvm/llvmregister.h   |   24 +-
 .../engine/internal/llvm/llvmregisterbase.h   |   26 +
 src/engine/internal/engine.cpp                |    4 +
 src/scratch/block.cpp                         |   17 +-
 src/scratch/inputvalue.cpp                    |   50 +-
 test/dev/compiler/compiler_test.cpp           |  716 ++--
 test/dev/llvm/llvmcodebuilder_test.cpp        | 3777 +++++++----------
 test/mocks/codebuildermock.h                  |   86 +-
 test/scratch_classes/block_test.cpp           |   12 +
 21 files changed, 2809 insertions(+), 2957 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmconstantregister.h
 create mode 100644 src/dev/engine/internal/llvm/llvmregisterbase.h

diff --git a/include/scratchcpp/block.h b/include/scratchcpp/block.h
index fe6cf382..76c70dc1 100644
--- a/include/scratchcpp/block.h
+++ b/include/scratchcpp/block.h
@@ -10,6 +10,9 @@ namespace libscratchcpp
 
 class IEngine;
 class Target;
+#ifdef USE_LLVM
+class CompilerValue;
+#endif
 class Input;
 class Field;
 class Comment;
@@ -25,7 +28,11 @@ class LIBSCRATCHCPP_EXPORT Block : public Entity
         Block(const std::string &id, const std::string &opcode);
         Block(const Block &) = delete;
 
+#ifdef USE_LLVM
+        CompilerValue *compile(Compiler *compiler);
+#else
         void compile(Compiler *compiler);
+#endif
 
         const std::string &opcode() const;
 
@@ -76,7 +83,7 @@ class LIBSCRATCHCPP_EXPORT Block : public Entity
         BlockComp compileFunction() const;
         void setCompileFunction(BlockComp newCompileFunction);
 
-        BlockComp hatPredicateCompileFunction() const;
+        HatPredicateCompileFunc hatPredicateCompileFunction() const;
         void setHatPredicateCompileFunction(HatPredicateCompileFunc newHatPredicateCompileFunction);
 
         bool mutationHasNext() const;
diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index b42f976d..ffc4ed7f 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -14,6 +14,8 @@ namespace libscratchcpp
 class IEngine;
 class Target;
 class ExecutableCode;
+class CompilerValue;
+class CompilerConstant;
 class Variable;
 class List;
 class Input;
@@ -33,6 +35,9 @@ class LIBSCRATCHCPP_EXPORT Compiler
             Unknown
         };
 
+        using ArgTypes = std::vector<StaticType>;
+        using Args = std::vector<CompilerValue *>;
+
         Compiler(IEngine *engine, Target *target);
         Compiler(const Compiler &) = delete;
 
@@ -42,63 +47,63 @@ class LIBSCRATCHCPP_EXPORT Compiler
 
         std::shared_ptr<ExecutableCode> compile(std::shared_ptr<Block> startBlock);
 
-        void addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const std::vector<StaticType> &argTypes = {});
-        void addConstValue(const Value &value);
-        void addVariableValue(Variable *variable);
-        void addListContents(List *list);
-        void addListItem(List *list);
-        void addListItemIndex(List *list);
-        void addListContains(List *list);
-        void addListSize(List *list);
-        void addInput(const std::string &name);
-
-        void createAdd();
-        void createSub();
-        void createMul();
-        void createDiv();
-
-        void createCmpEQ();
-        void createCmpGT();
-        void createCmpLT();
-
-        void createAnd();
-        void createOr();
-        void createNot();
-
-        void createMod();
-        void createRound();
-        void createAbs();
-        void createFloor();
-        void createCeil();
-        void createSqrt();
-        void createSin();
-        void createCos();
-        void createTan();
-        void createAsin();
-        void createAcos();
-        void createAtan();
-        void createLn();
-        void createLog10();
-        void createExp();
-        void createExp10();
-
-        void createVariableWrite(Variable *variable);
+        CompilerValue *addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
+        CompilerConstant *addConstValue(const Value &value);
+        CompilerValue *addVariableValue(Variable *variable);
+        CompilerValue *addListContents(List *list);
+        CompilerValue *addListItem(List *list, CompilerValue *index);
+        CompilerValue *addListItemIndex(List *list, CompilerValue *item);
+        CompilerValue *addListContains(List *list, CompilerValue *item);
+        CompilerValue *addListSize(List *list);
+        CompilerValue *addInput(const std::string &name);
+
+        CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2);
+        CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2);
+        CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2);
+        CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2);
+
+        CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2);
+        CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2);
+        CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2);
+
+        CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2);
+        CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2);
+        CompilerValue *createNot(CompilerValue *operand);
+
+        CompilerValue *createMod(CompilerValue *num1, CompilerValue *num2);
+        CompilerValue *createRound(CompilerValue *num);
+        CompilerValue *createAbs(CompilerValue *num);
+        CompilerValue *createFloor(CompilerValue *num);
+        CompilerValue *createCeil(CompilerValue *num);
+        CompilerValue *createSqrt(CompilerValue *num);
+        CompilerValue *createSin(CompilerValue *num);
+        CompilerValue *createCos(CompilerValue *num);
+        CompilerValue *createTan(CompilerValue *num);
+        CompilerValue *createAsin(CompilerValue *num);
+        CompilerValue *createAcos(CompilerValue *num);
+        CompilerValue *createAtan(CompilerValue *num);
+        CompilerValue *createLn(CompilerValue *num);
+        CompilerValue *createLog10(CompilerValue *num);
+        CompilerValue *createExp(CompilerValue *num);
+        CompilerValue *createExp10(CompilerValue *num);
+
+        void createVariableWrite(Variable *variable, CompilerValue *value);
 
         void createListClear(List *list);
-        void createListRemove(List *list);
-        void createListAppend(List *list);
-        void createListInsert(List *list);
-        void createListReplace(List *list);
+        void createListRemove(List *list, CompilerValue *index);
+        void createListAppend(List *list, CompilerValue *item);
+        void createListInsert(List *list, CompilerValue *index, CompilerValue *item);
+        void createListReplace(List *list, CompilerValue *index, CompilerValue *item);
 
-        void beginIfStatement();
+        void beginIfStatement(CompilerValue *cond);
         void beginElseBranch();
         void endIf();
 
-        void moveToIf(std::shared_ptr<Block> substack);
-        void moveToIfElse(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2);
-        void moveToRepeatLoop(std::shared_ptr<Block> substack);
-        void moveToWhileLoop(std::shared_ptr<Block> substack);
-        void moveToRepeatUntilLoop(std::shared_ptr<Block> substack);
+        void moveToIf(CompilerValue *cond, std::shared_ptr<Block> substack);
+        void moveToIfElse(CompilerValue *cond, std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2);
+        void moveToRepeatLoop(CompilerValue *count, std::shared_ptr<Block> substack);
+        void moveToWhileLoop(CompilerValue *cond, std::shared_ptr<Block> substack);
+        void moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block> substack);
         void beginLoopCondition();
         void warp();
 
@@ -108,7 +113,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         const std::unordered_set<std::string> &unsupportedBlocks() const;
 
     private:
-        void addInput(Input *input);
+        CompilerValue *addInput(Input *input);
 
         spimpl::unique_impl_ptr<CompilerPrivate> impl;
 };
diff --git a/include/scratchcpp/global.h b/include/scratchcpp/global.h
index c72c14f6..e5824008 100644
--- a/include/scratchcpp/global.h
+++ b/include/scratchcpp/global.h
@@ -25,6 +25,9 @@ namespace libscratchcpp
 
 class VirtualMachine;
 class Compiler;
+#ifdef USE_LLVM
+class CompilerValue;
+#endif
 class Block;
 class Value;
 
@@ -35,12 +38,21 @@ class Value;
  */
 using BlockFunc = unsigned int (*)(VirtualMachine *vm);
 
+#ifdef USE_LLVM
+/*!
+ * \typedef BlockComp
+ *
+ * BlockComp is a function pointer for functions which are used to compile blocks.
+ */
+using BlockComp = CompilerValue *(*)(Compiler *);
+#else
 /*!
  * \typedef BlockComp
  *
  * BlockComp is a function pointer for functions which are used to compile blocks to bytecode.
  */
 using BlockComp = void (*)(Compiler *);
+#endif // USE_LLVM
 
 /*!
  * \typedef MonitorNameFunc
@@ -56,12 +68,21 @@ using MonitorNameFunc = const std::string &(*)(Block *);
  */
 using MonitorChangeFunc = void (*)(Block *, const Value &newValue);
 
+#ifdef USE_LLVM
+/*!
+ * \typedef HatPredicateCompileFunc
+ *
+ * HatPredicateCompileFunc is a function pointer for functions which are used to compile edge-activated hat predicates.
+ */
+using HatPredicateCompileFunc = CompilerValue *(*)(Compiler *vm);
+#else
 /*!
  * \typedef HatPredicateCompileFunc
  *
  * HatPredicateCompileFunc is a function pointer for functions which are used to compile edge-activated hat predicates to bytecode.
  */
 using HatPredicateCompileFunc = void (*)(Compiler *vm);
+#endif // USE_LLVM
 
 } // namespace libscratchcpp
 
diff --git a/include/scratchcpp/inputvalue.h b/include/scratchcpp/inputvalue.h
index 32c4cb0f..f1580c75 100644
--- a/include/scratchcpp/inputvalue.h
+++ b/include/scratchcpp/inputvalue.h
@@ -10,6 +10,9 @@
 namespace libscratchcpp
 {
 
+#ifdef USE_LLVM
+class CompilerValue;
+#endif
 class Block;
 class Entity;
 class InputValuePrivate;
@@ -34,7 +37,11 @@ class LIBSCRATCHCPP_EXPORT InputValue
 
         InputValue(Type type = Type::Number);
 
+#ifdef USE_LLVM
+        CompilerValue *compile(Compiler *compiler);
+#else
         void compile(Compiler *compiler);
+#endif
 
         Type type() const;
         void setType(Type newType);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index c8f134fa..51583120 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/input.h>
 #include <scratchcpp/inputvalue.h>
@@ -77,219 +78,220 @@ std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBl
  * Adds a call to the given function.\n
  * For example: extern "C" bool some_block(Target *target, double arg1, const char *arg2)
  */
-void Compiler::addFunctionCall(const std::string &functionName, StaticType returnType, const std::vector<StaticType> &argTypes)
+CompilerValue *Compiler::addFunctionCall(const std::string &functionName, StaticType returnType, const ArgTypes &argTypes, const Args &args)
 {
-    impl->builder->addFunctionCall(functionName, returnType, argTypes);
+    assert(argTypes.size() == args.size());
+    return impl->builder->addFunctionCall(functionName, returnType, argTypes, args);
 }
 
-/*! Adds a constant value to the compiled code. */
-void Compiler::addConstValue(const Value &value)
+/*! Adds the given constant to the compiled code. */
+CompilerConstant *Compiler::addConstValue(const Value &value)
 {
-    impl->builder->addConstValue(value);
+    return static_cast<CompilerConstant *>(impl->builder->addConstValue(value));
 }
 
-/*! Adds the given variable to the code (to read it). */
-void Compiler::addVariableValue(Variable *variable)
+/*! Adds the value of the given variable to the code. */
+CompilerValue *Compiler::addVariableValue(Variable *variable)
 {
-    impl->builder->addVariableValue(variable);
+    return impl->builder->addVariableValue(variable);
 }
 
-/*! Adds the given list to the code (to read its string representation). */
-void Compiler::addListContents(List *list)
+/*! Adds the string representation of the given list to the code. */
+CompilerValue *Compiler::addListContents(List *list)
 {
-    impl->builder->addListContents(list);
+    return impl->builder->addListContents(list);
 }
 
-/*! Adds the item with index from the last value of the given list to the code. */
-void Compiler::addListItem(List *list)
+/*! Adds the item with index of the given list to the code. */
+CompilerValue *Compiler::addListItem(List *list, CompilerValue *index)
 {
-    impl->builder->addListItem(list);
+    return impl->builder->addListItem(list, index);
 }
 
-/*! Adds the index of the item from the last value of the given list to the code. */
-void Compiler::addListItemIndex(List *list)
+/*! Adds the index of item of the given list to the code. */
+CompilerValue *Compiler::addListItemIndex(List *list, CompilerValue *item)
 {
-    impl->builder->addListItemIndex(list);
+    return impl->builder->addListItemIndex(list, item);
 }
 
-/*! Adds the result of a list contains item from the  check to the code. */
-void Compiler::addListContains(List *list)
+/*! Adds the result of a list contains item check to the code. */
+CompilerValue *Compiler::addListContains(List *list, CompilerValue *item)
 {
-    impl->builder->addListContains(list);
+    return impl->builder->addListContains(list, item);
 }
 
 /*! Adds the length of the given list to the code. */
-void Compiler::addListSize(List *list)
+CompilerValue *Compiler::addListSize(List *list)
 {
-    impl->builder->addListSize(list);
+    return impl->builder->addListSize(list);
 }
 
 /*! Compiles the given input (resolved by name) and adds it to the compiled code. */
-void Compiler::addInput(const std::string &name)
+CompilerValue *Compiler::addInput(const std::string &name)
 {
-    addInput(impl->block->inputAt(impl->block->findInput(name)).get());
+    return addInput(impl->block->inputAt(impl->block->findInput(name)).get());
 }
 
-/*! Creates an add instruction from the last 2 values. */
-void Compiler::createAdd()
+/*! Creates an add instruction. */
+CompilerValue *Compiler::createAdd(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createAdd();
+    return impl->builder->createAdd(operand1, operand2);
 }
 
-/*! Creates a subtract instruction from the last 2 values. */
-void Compiler::createSub()
+/*! Creates a subtract instruction. */
+CompilerValue *Compiler::createSub(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createSub();
+    return impl->builder->createSub(operand1, operand2);
 }
 
-/*! Creates a multiply instruction from the last 2 values. */
-void Compiler::createMul()
+/*! Creates a multiply instruction. */
+CompilerValue *Compiler::createMul(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createMul();
+    return impl->builder->createMul(operand1, operand2);
 }
 
-/*! Creates a divide instruction from the last 2 values. */
-void Compiler::createDiv()
+/*! Creates a divide instruction. */
+CompilerValue *Compiler::createDiv(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createDiv();
+    return impl->builder->createDiv(operand1, operand2);
 }
 
-/*! Creates an equality comparison instruction using the last 2 values. */
-void Compiler::createCmpEQ()
+/*! Creates an equality comparison instruction. */
+CompilerValue *Compiler::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createCmpEQ();
+    return impl->builder->createCmpEQ(operand1, operand2);
 }
 
-/*! Creates a greater than comparison instruction using the last 2 values. */
-void Compiler::createCmpGT()
+/*! Creates a greater than comparison instruction. */
+CompilerValue *Compiler::createCmpGT(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createCmpGT();
+    return impl->builder->createCmpGT(operand1, operand2);
 }
 
-/*! Creates a lower than comparison instruction using the last 2 values. */
-void Compiler::createCmpLT()
+/*! Creates a lower than comparison instruction. */
+CompilerValue *Compiler::createCmpLT(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createCmpLT();
+    return impl->builder->createCmpLT(operand1, operand2);
 }
 
-/*! Creates an AND operation using the last 2 values. */
-void Compiler::createAnd()
+/*! Creates an AND operation. */
+CompilerValue *Compiler::createAnd(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createAnd();
+    return impl->builder->createAnd(operand1, operand2);
 }
 
-/*! Creates an OR operation using the last 2 values. */
-void Compiler::createOr()
+/*! Creates an OR operation. */
+CompilerValue *Compiler::createOr(CompilerValue *operand1, CompilerValue *operand2)
 {
-    impl->builder->createOr();
+    return impl->builder->createOr(operand1, operand2);
 }
 
-/*! Creates a NOT operation using the last value. */
-void Compiler::createNot()
+/*! Creates a NOT operation. */
+CompilerValue *Compiler::createNot(CompilerValue *operand)
 {
-    impl->builder->createNot();
+    return impl->builder->createNot(operand);
 }
 
-/*! Creates a remainder operation using the last 2 values. */
-void Compiler::createMod()
+/*! Creates a remainder operation. */
+CompilerValue *Compiler::createMod(CompilerValue *num1, CompilerValue *num2)
 {
-    impl->builder->createMod();
+    return impl->builder->createMod(num1, num2);
 }
 
-/*! Creates a round operation using the last value. */
-void Compiler::createRound()
+/*! Creates a round operation. */
+CompilerValue *Compiler::createRound(CompilerValue *num)
 {
-    impl->builder->createRound();
+    return impl->builder->createRound(num);
 }
 
-/*! Creates an abs operation using the last value. */
-void Compiler::createAbs()
+/*! Creates an abs operation. */
+CompilerValue *Compiler::createAbs(CompilerValue *num)
 {
-    impl->builder->createAbs();
+    return impl->builder->createAbs(num);
 }
 
-/*! Creates a floor operation using the last value. */
-void Compiler::createFloor()
+/*! Creates a floor operation. */
+CompilerValue *Compiler::createFloor(CompilerValue *num)
 {
-    impl->builder->createFloor();
+    return impl->builder->createFloor(num);
 }
 
-/*! Creates a ceiling operation using the last value. */
-void Compiler::createCeil()
+/*! Creates a ceiling operation. */
+CompilerValue *Compiler::createCeil(CompilerValue *num)
 {
-    impl->builder->createCeil();
+    return impl->builder->createCeil(num);
 }
 
-/*! Creates a square root operation using the last value. */
-void Compiler::createSqrt()
+/*! Creates a square root operation. */
+CompilerValue *Compiler::createSqrt(CompilerValue *num)
 {
-    impl->builder->createSqrt();
+    return impl->builder->createSqrt(num);
 }
 
-/*! Creates a sin operation using the last value. */
-void Compiler::createSin()
+/*! Creates a sin operation. */
+CompilerValue *Compiler::createSin(CompilerValue *num)
 {
-    impl->builder->createSin();
+    return impl->builder->createSin(num);
 }
 
-/*! Creates a cos operation using the last value. */
-void Compiler::createCos()
+/*! Creates a cos operation. */
+CompilerValue *Compiler::createCos(CompilerValue *num)
 {
-    impl->builder->createCos();
+    return impl->builder->createCos(num);
 }
 
-/*! Creates a tan operation using the last value. */
-void Compiler::createTan()
+/*! Creates a tan operation. */
+CompilerValue *Compiler::createTan(CompilerValue *num)
 {
-    impl->builder->createTan();
+    return impl->builder->createTan(num);
 }
 
-/*! Creates an asin operation using the last value. */
-void Compiler::createAsin()
+/*! Creates an asin operation. */
+CompilerValue *Compiler::createAsin(CompilerValue *num)
 {
-    impl->builder->createAsin();
+    return impl->builder->createAsin(num);
 }
 
-/*! Creates an acos operation using the last value. */
-void Compiler::createAcos()
+/*! Creates an acos operation. */
+CompilerValue *Compiler::createAcos(CompilerValue *num)
 {
-    impl->builder->createAcos();
+    return impl->builder->createAcos(num);
 }
 
-/*! Creates an atan operation using the last value. */
-void Compiler::createAtan()
+/*! Creates an atan operation. */
+CompilerValue *Compiler::createAtan(CompilerValue *num)
 {
-    impl->builder->createAtan();
+    return impl->builder->createAtan(num);
 }
 
-/*! Creates an ln operation using the last value. */
-void Compiler::createLn()
+/*! Creates an ln operation. */
+CompilerValue *Compiler::createLn(CompilerValue *num)
 {
-    impl->builder->createLn();
+    return impl->builder->createLn(num);
 }
 
-/*! Creates a log10 operation using the last value. */
-void Compiler::createLog10()
+/*! Creates a log10 operation. */
+CompilerValue *Compiler::createLog10(CompilerValue *num)
 {
-    impl->builder->createLog10();
+    return impl->builder->createLog10(num);
 }
 
-/*! Creates an e^x operation using the last value. */
-void Compiler::createExp()
+/*! Creates an e^x operation. */
+CompilerValue *Compiler::createExp(CompilerValue *num)
 {
-    impl->builder->createExp();
+    return impl->builder->createExp(num);
 }
 
-/*! Creates a 10^x operation using the last value. */
-void Compiler::createExp10()
+/*! Creates a 10^x operation. */
+CompilerValue *Compiler::createExp10(CompilerValue *num)
 {
-    impl->builder->createExp10();
+    return impl->builder->createExp10(num);
 }
 
-/*! Creates a variable write operation using the last value. */
-void Compiler::createVariableWrite(Variable *variable)
+/*! Creates a variable write operation. */
+void Compiler::createVariableWrite(Variable *variable, CompilerValue *value)
 {
-    impl->builder->createVariableWrite(variable);
+    impl->builder->createVariableWrite(variable, value);
 }
 
 /*! Creates a clear list operation. */
@@ -302,36 +304,36 @@ void Compiler::createListClear(List *list)
  * Creates a remove item from list operation.
  * \note The index starts with 0 and is cast to number, special strings like "last" are not handled.
  */
-void Compiler::createListRemove(List *list)
+void Compiler::createListRemove(List *list, CompilerValue *index)
 {
-    impl->builder->createListRemove(list);
+    impl->builder->createListRemove(list, index);
 }
 
-/*! Creates a list append operation using the last value. */
-void Compiler::createListAppend(List *list)
+/*! Creates a list append operation. */
+void Compiler::createListAppend(List *list, CompilerValue *item)
 {
-    impl->builder->createListAppend(list);
+    impl->builder->createListAppend(list, item);
 }
 
-/*! Creates a list insert operation using the last 2 values (index, value). */
-void Compiler::createListInsert(List *list)
+/*! Creates a list insert operation. */
+void Compiler::createListInsert(List *list, CompilerValue *index, CompilerValue *item)
 {
-    impl->builder->createListInsert(list);
+    impl->builder->createListInsert(list, index, item);
 }
 
-/*! Creates a list replace operation using the last 2 values (index, value). */
-void Compiler::createListReplace(List *list)
+/*! Creates a list replace operation. */
+void Compiler::createListReplace(List *list, CompilerValue *index, CompilerValue *item)
 {
-    impl->builder->createListReplace(list);
+    impl->builder->createListReplace(list, index, item);
 }
 
 /*!
  * Starts a custom if statement.
  * \note The if statement must be terminated using endIf() after compiling your block.
  */
-void Compiler::beginIfStatement()
+void Compiler::beginIfStatement(CompilerValue *cond)
 {
-    impl->builder->beginIfStatement();
+    impl->builder->beginIfStatement(cond);
     impl->customIfStatementCount++;
 }
 
@@ -355,7 +357,7 @@ void Compiler::endIf()
 }
 
 /*! Jumps to the given if substack. */
-void Compiler::moveToIf(std::shared_ptr<Block> substack)
+void Compiler::moveToIf(CompilerValue *cond, std::shared_ptr<Block> substack)
 {
     if (!substack)
         return; // ignore empty if statements
@@ -363,11 +365,11 @@ void Compiler::moveToIf(std::shared_ptr<Block> substack)
     impl->substackHit = true;
     impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::IfStatement });
     impl->block = substack;
-    impl->builder->beginIfStatement();
+    impl->builder->beginIfStatement(cond);
 }
 
 /*! Jumps to the given if/else substack. The second substack is used for the else branch. */
-void Compiler::moveToIfElse(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2)
+void Compiler::moveToIfElse(CompilerValue *cond, std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2)
 {
     if (!substack1 && !substack2)
         return; // ignore empty if statements
@@ -375,43 +377,43 @@ void Compiler::moveToIfElse(std::shared_ptr<Block> substack1, std::shared_ptr<Bl
     impl->substackHit = true;
     impl->substackTree.push_back({ { impl->block, substack2 }, CompilerPrivate::SubstackType::IfStatement });
     impl->block = substack1;
-    impl->builder->beginIfStatement();
+    impl->builder->beginIfStatement(cond);
 
     if (!impl->block)
         impl->substackEnd();
 }
 
 /*! Jumps to the given repeat loop substack. */
-void Compiler::moveToRepeatLoop(std::shared_ptr<Block> substack)
+void Compiler::moveToRepeatLoop(CompilerValue *count, std::shared_ptr<Block> substack)
 {
     impl->substackHit = true;
     impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop });
     impl->block = substack;
-    impl->builder->beginRepeatLoop();
+    impl->builder->beginRepeatLoop(count);
 
     if (!impl->block)
         impl->substackEnd();
 }
 
 /*! Jumps to the given while loop substack. */
-void Compiler::moveToWhileLoop(std::shared_ptr<Block> substack)
+void Compiler::moveToWhileLoop(CompilerValue *cond, std::shared_ptr<Block> substack)
 {
     impl->substackHit = true;
     impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop });
     impl->block = substack;
-    impl->builder->beginWhileLoop();
+    impl->builder->beginWhileLoop(cond);
 
     if (!impl->block)
         impl->substackEnd();
 }
 
 /*! Jumps to the given until loop substack. */
-void Compiler::moveToRepeatUntilLoop(std::shared_ptr<Block> substack)
+void Compiler::moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block> substack)
 {
     impl->substackHit = true;
     impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop });
     impl->block = substack;
-    impl->builder->beginRepeatUntilLoop();
+    impl->builder->beginRepeatUntilLoop(cond);
 
     if (!impl->block)
         impl->substackEnd();
@@ -447,55 +449,57 @@ const std::unordered_set<std::string> &Compiler::unsupportedBlocks() const
     return impl->unsupportedBlocks;
 }
 
-void Compiler::addInput(Input *input)
+CompilerValue *Compiler::addInput(Input *input)
 {
-    if (!input) {
-        addConstValue(Value());
-        return;
-    }
+    if (!input)
+        return addConstValue(Value());
 
     switch (input->type()) {
         case Input::Type::Shadow:
         case Input::Type::NoShadow: {
             if (input->pointsToDropdownMenu())
-                addConstValue(input->selectedMenuItem());
+                return addConstValue(input->selectedMenuItem());
             else {
+                CompilerValue *ret = nullptr;
                 auto previousBlock = impl->block;
                 impl->block = input->valueBlock();
 
                 if (impl->block) {
                     if (impl->block->compileFunction())
-                        impl->block->compile(this);
+                        ret = impl->block->compile(this);
                     else {
                         std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl;
                         impl->unsupportedBlocks.insert(impl->block->opcode());
-                        addConstValue(Value());
+                        ret = addConstValue(Value());
                     }
                 } else
-                    addConstValue(input->primaryValue()->value());
+                    ret = addConstValue(input->primaryValue()->value());
 
                 impl->block = previousBlock;
+                return ret;
             }
-
-            break;
         }
 
         case Input::Type::ObscuredShadow: {
+            CompilerValue *ret = nullptr;
             auto previousBlock = impl->block;
             impl->block = input->valueBlock();
+
             if (impl->block) {
                 if (impl->block->compileFunction())
-                    impl->block->compile(this);
+                    ret = impl->block->compile(this);
                 else {
                     std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl;
                     impl->unsupportedBlocks.insert(impl->block->opcode());
-                    addConstValue(Value());
+                    ret = addConstValue(Value());
                 }
             } else
-                input->primaryValue()->compile(this);
+                ret = input->primaryValue()->compile(this);
 
             impl->block = previousBlock;
-            break;
+            return ret;
         }
     }
+
+    return nullptr;
 }
diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h
index 0a23d71d..080afc96 100644
--- a/src/dev/engine/compiler_p.h
+++ b/src/dev/engine/compiler_p.h
@@ -12,6 +12,7 @@ namespace libscratchcpp
 class IEngine;
 class Target;
 class Block;
+class CompilerValue;
 class ICodeBuilderFactory;
 class ICodeBuilder;
 
diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 30d60cfe..4e966667 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -19,60 +19,60 @@ class ICodeBuilder
 
         virtual std::shared_ptr<ExecutableCode> finalize() = 0;
 
-        virtual void addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector<Compiler::StaticType> &argTypes) = 0;
-        virtual void addConstValue(const Value &value) = 0;
-        virtual void addVariableValue(Variable *variable) = 0;
-        virtual void addListContents(List *list) = 0;
-        virtual void addListItem(List *list) = 0;
-        virtual void addListItemIndex(List *list) = 0;
-        virtual void addListContains(List *list) = 0;
-        virtual void addListSize(List *list) = 0;
-
-        virtual void createAdd() = 0;
-        virtual void createSub() = 0;
-        virtual void createMul() = 0;
-        virtual void createDiv() = 0;
-
-        virtual void createCmpEQ() = 0;
-        virtual void createCmpGT() = 0;
-        virtual void createCmpLT() = 0;
-
-        virtual void createAnd() = 0;
-        virtual void createOr() = 0;
-        virtual void createNot() = 0;
-
-        virtual void createMod() = 0;
-        virtual void createRound() = 0;
-        virtual void createAbs() = 0;
-        virtual void createFloor() = 0;
-        virtual void createCeil() = 0;
-        virtual void createSqrt() = 0;
-        virtual void createSin() = 0;
-        virtual void createCos() = 0;
-        virtual void createTan() = 0;
-        virtual void createAsin() = 0;
-        virtual void createAcos() = 0;
-        virtual void createAtan() = 0;
-        virtual void createLn() = 0;
-        virtual void createLog10() = 0;
-        virtual void createExp() = 0;
-        virtual void createExp10() = 0;
-
-        virtual void createVariableWrite(Variable *variable) = 0;
+        virtual CompilerValue *addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0;
+        virtual CompilerConstant *addConstValue(const Value &value) = 0;
+        virtual CompilerValue *addVariableValue(Variable *variable) = 0;
+        virtual CompilerValue *addListContents(List *list) = 0;
+        virtual CompilerValue *addListItem(List *list, CompilerValue *index) = 0;
+        virtual CompilerValue *addListItemIndex(List *list, CompilerValue *item) = 0;
+        virtual CompilerValue *addListContains(List *list, CompilerValue *item) = 0;
+        virtual CompilerValue *addListSize(List *list) = 0;
+
+        virtual CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2) = 0;
+        virtual CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2) = 0;
+        virtual CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) = 0;
+        virtual CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) = 0;
+
+        virtual CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) = 0;
+        virtual CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) = 0;
+        virtual CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) = 0;
+
+        virtual CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2) = 0;
+        virtual CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2) = 0;
+        virtual CompilerValue *createNot(CompilerValue *operand) = 0;
+
+        virtual CompilerValue *createMod(CompilerValue *num1, CompilerValue *num2) = 0;
+        virtual CompilerValue *createRound(CompilerValue *num) = 0;
+        virtual CompilerValue *createAbs(CompilerValue *num) = 0;
+        virtual CompilerValue *createFloor(CompilerValue *num) = 0;
+        virtual CompilerValue *createCeil(CompilerValue *num) = 0;
+        virtual CompilerValue *createSqrt(CompilerValue *num) = 0;
+        virtual CompilerValue *createSin(CompilerValue *num) = 0;
+        virtual CompilerValue *createCos(CompilerValue *num) = 0;
+        virtual CompilerValue *createTan(CompilerValue *num) = 0;
+        virtual CompilerValue *createAsin(CompilerValue *num) = 0;
+        virtual CompilerValue *createAcos(CompilerValue *num) = 0;
+        virtual CompilerValue *createAtan(CompilerValue *num) = 0;
+        virtual CompilerValue *createLn(CompilerValue *num) = 0;
+        virtual CompilerValue *createLog10(CompilerValue *num) = 0;
+        virtual CompilerValue *createExp(CompilerValue *num) = 0;
+        virtual CompilerValue *createExp10(CompilerValue *num) = 0;
+
+        virtual void createVariableWrite(Variable *variable, CompilerValue *value) = 0;
 
         virtual void createListClear(List *list) = 0;
-        virtual void createListRemove(List *list) = 0;
-        virtual void createListAppend(List *list) = 0;
-        virtual void createListInsert(List *list) = 0;
-        virtual void createListReplace(List *list) = 0;
+        virtual void createListRemove(List *list, CompilerValue *index) = 0;
+        virtual void createListAppend(List *list, CompilerValue *item) = 0;
+        virtual void createListInsert(List *list, CompilerValue *index, CompilerValue *item) = 0;
+        virtual void createListReplace(List *list, CompilerValue *index, CompilerValue *item) = 0;
 
-        virtual void beginIfStatement() = 0;
+        virtual void beginIfStatement(CompilerValue *cond) = 0;
         virtual void beginElseBranch() = 0;
         virtual void endIf() = 0;
 
-        virtual void beginRepeatLoop() = 0;
-        virtual void beginWhileLoop() = 0;
-        virtual void beginRepeatUntilLoop() = 0;
+        virtual void beginRepeatLoop(CompilerValue *count) = 0;
+        virtual void beginWhileLoop(CompilerValue *cond) = 0;
+        virtual void beginRepeatUntilLoop(CompilerValue *cond) = 0;
         virtual void beginLoopCondition() = 0;
         virtual void endLoop() = 0;
 
diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index 1a4ce89a..4ef97a05 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -2,7 +2,9 @@ target_sources(scratchcpp
   PRIVATE
     llvmcodebuilder.cpp
     llvmcodebuilder.h
+    llvmregisterbase.h
     llvmregister.h
+    llvmconstantregister.h
     llvminstruction.h
     llvmifstatement.h
     llvmloop.h
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index d6943ba6..864dc252 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -9,9 +9,11 @@
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
+#include <scratchcpp/dev/compilerconstant.h>
 
 #include "llvmcodebuilder.h"
 #include "llvmexecutablecode.h"
+#include "llvmconstantregister.h"
 #include "llvmifstatement.h"
 #include "llvmloop.h"
 #include "llvmtypes.h"
@@ -33,8 +35,6 @@ LLVMCodeBuilder::LLVMCodeBuilder(Target *target, const std::string &id, bool war
     llvm::InitializeNativeTargetAsmPrinter();
     llvm::InitializeNativeTargetAsmParser();
 
-    m_constValues.push_back({});
-    m_regs.push_back({});
     initTypes();
     createVariableMap();
     createListMap();
@@ -127,13 +127,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     args.push_back(castValue(arg.second, arg.first));
                 }
 
-                llvm::Type *retType = getType(step.functionReturnReg ? step.functionReturnReg->type : Compiler::StaticType::Void);
+                llvm::Type *retType = getType(step.functionReturnReg ? step.functionReturnReg->type() : Compiler::StaticType::Void);
                 llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(retType, types, false)), args);
 
                 if (step.functionReturnReg) {
                     step.functionReturnReg->value = ret;
 
-                    if (step.functionReturnReg->type == Compiler::StaticType::String)
+                    if (step.functionReturnReg->type() == Compiler::StaticType::String)
                         m_heap.push_back(step.functionReturnReg->value);
                 }
 
@@ -484,7 +484,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 0);
                 const LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
                 step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr;
-                step.functionReturnReg->type = varPtr.type;
+                step.functionReturnReg->setType(varPtr.type);
                 break;
             }
 
@@ -617,7 +617,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
                 step.functionReturnReg->value = getListItem(listPtr, index, func);
-                step.functionReturnReg->type = listPtr.type;
+                step.functionReturnReg->setType(listPtr.type);
                 break;
             }
 
@@ -878,12 +878,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     else
         coro->end();
 
-    if (!m_tmpRegs.empty()) {
-        std::cout
-            << "warning: " << m_tmpRegs.size() << " registers were leaked by script '" << m_module->getName().str() << "', function '" << func->getName().str()
-            << "' (if you see this as a regular user, this is a bug and should be reported)" << std::endl;
-    }
-
     verifyFunction(func);
 
     // Create resume function
@@ -913,40 +907,36 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     return std::make_shared<LLVMExecutableCode>(std::move(m_module));
 }
 
-void LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector<Compiler::StaticType> &argTypes)
+CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
 {
+    assert(argTypes.size() == args.size());
+
     LLVMInstruction ins(LLVMInstruction::Type::FunctionCall);
     ins.functionName = functionName;
 
-    assert(m_tmpRegs.size() >= argTypes.size());
-    size_t j = 0;
-
-    for (size_t i = m_tmpRegs.size() - argTypes.size(); i < m_tmpRegs.size(); i++)
-        ins.args.push_back({ argTypes[j++], m_tmpRegs[i] });
-
-    m_tmpRegs.erase(m_tmpRegs.end() - argTypes.size(), m_tmpRegs.end());
+    for (size_t i = 0; i < args.size(); i++)
+        ins.args.push_back({ argTypes[i], static_cast<LLVMRegister *>(args[i]) });
 
     if (returnType != Compiler::StaticType::Void) {
         auto reg = std::make_shared<LLVMRegister>(returnType);
         reg->isRawValue = true;
-        ins.functionReturnReg = reg;
-        m_regs[m_currentFunction].push_back(reg);
-        m_tmpRegs.push_back(reg);
+        ins.functionReturnReg = reg.get();
+        m_instructions.push_back(ins);
+        return addReg(reg);
     }
 
     m_instructions.push_back(ins);
+    return nullptr;
 }
 
-void LLVMCodeBuilder::addConstValue(const Value &value)
+CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value)
 {
-    auto reg = std::make_shared<LLVMRegister>(TYPE_MAP[value.type()]);
-    reg->isConstValue = true;
-    reg->constValue = value;
-    m_regs[m_currentFunction].push_back(reg);
-    m_tmpRegs.push_back(reg);
+    auto constReg = std::make_shared<LLVMConstantRegister>(TYPE_MAP[value.type()], value);
+    auto reg = std::reinterpret_pointer_cast<LLVMRegister>(constReg);
+    return static_cast<CompilerConstant *>(addReg(reg));
 }
 
-void LLVMCodeBuilder::addVariableValue(Variable *variable)
+CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
     LLVMInstruction ins(LLVMInstruction::Type::ReadVariable);
     ins.workVariable = variable;
@@ -954,236 +944,239 @@ void LLVMCodeBuilder::addVariableValue(Variable *variable)
 
     auto ret = std::make_shared<LLVMRegister>(Compiler::StaticType::Unknown);
     ret->isRawValue = false;
-    ins.functionReturnReg = ret;
-    m_regs[m_currentFunction].push_back(ret);
-    m_tmpRegs.push_back(ret);
+    ins.functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
+    return addReg(ret);
 }
 
-void LLVMCodeBuilder::addListContents(List *list)
+CompilerValue *LLVMCodeBuilder::addListContents(List *list)
 {
+    return addConstValue(Value());
 }
 
-void LLVMCodeBuilder::addListItem(List *list)
+CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
 {
     LLVMInstruction ins(LLVMInstruction::Type::GetListItem);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
 
-    assert(m_tmpRegs.size() >= 1);
-    ins.args.push_back({ Compiler::StaticType::Number, m_tmpRegs[0] });
-
-    m_tmpRegs.erase(m_tmpRegs.end() - 1, m_tmpRegs.end());
+    ins.args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(index) });
 
     auto ret = std::make_shared<LLVMRegister>(Compiler::StaticType::Unknown);
     ret->isRawValue = false;
-    ins.functionReturnReg = ret;
-    m_regs[m_currentFunction].push_back(ret);
-    m_tmpRegs.push_back(ret);
+    ins.functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
+    return addReg(ret);
 }
 
-void LLVMCodeBuilder::addListItemIndex(List *list)
+CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListItemIndex, Compiler::StaticType::Number, Compiler::StaticType::Unknown, 1);
+    LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    return createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item });
 }
 
-void LLVMCodeBuilder::addListContains(List *list)
+CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::ListContainsItem, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, 1);
+    LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    return createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item });
 }
 
-void LLVMCodeBuilder::addListSize(List *list)
+CompilerValue *LLVMCodeBuilder::addListSize(List *list)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::GetListSize, Compiler::StaticType::Number, {}, 0);
+    LLVMInstruction ins(LLVMInstruction::Type::GetListSize);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    return createOp(ins, Compiler::StaticType::Number);
 }
 
-void LLVMCodeBuilder::createAdd()
+CompilerValue *LLVMCodeBuilder::createAdd(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createSub()
+CompilerValue *LLVMCodeBuilder::createSub(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::Sub, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::Sub, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createMul()
+CompilerValue *LLVMCodeBuilder::createMul(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::Mul, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::Mul, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createDiv()
+CompilerValue *LLVMCodeBuilder::createDiv(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createCmpEQ()
+CompilerValue *LLVMCodeBuilder::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createCmpGT()
+CompilerValue *LLVMCodeBuilder::createCmpGT(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::CmpGT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::CmpGT, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createCmpLT()
+CompilerValue *LLVMCodeBuilder::createCmpLT(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createAnd()
+CompilerValue *LLVMCodeBuilder::createAnd(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2);
+    return createOp(LLVMInstruction::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createOr()
+CompilerValue *LLVMCodeBuilder::createOr(CompilerValue *operand1, CompilerValue *operand2)
 {
-    createOp(LLVMInstruction::Type::Or, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2);
+    return createOp(LLVMInstruction::Type::Or, Compiler::StaticType::Bool, Compiler::StaticType::Bool, { operand1, operand2 });
 }
 
-void LLVMCodeBuilder::createNot()
+CompilerValue *LLVMCodeBuilder::createNot(CompilerValue *operand)
 {
-    createOp(LLVMInstruction::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 1);
+    return createOp(LLVMInstruction::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, { operand });
 }
 
-void LLVMCodeBuilder::createMod()
+CompilerValue *LLVMCodeBuilder::createMod(CompilerValue *num1, CompilerValue *num2)
 {
-    createOp(LLVMInstruction::Type::Mod, Compiler::StaticType::Number, Compiler::StaticType::Number, 2);
+    return createOp(LLVMInstruction::Type::Mod, Compiler::StaticType::Number, Compiler::StaticType::Number, { num1, num2 });
 }
 
-void LLVMCodeBuilder::createRound()
+CompilerValue *LLVMCodeBuilder::createRound(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Round, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Round, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createAbs()
+CompilerValue *LLVMCodeBuilder::createAbs(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Abs, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Abs, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createFloor()
+CompilerValue *LLVMCodeBuilder::createFloor(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Floor, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Floor, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createCeil()
+CompilerValue *LLVMCodeBuilder::createCeil(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Ceil, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Ceil, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createSqrt()
+CompilerValue *LLVMCodeBuilder::createSqrt(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Sqrt, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Sqrt, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createSin()
+CompilerValue *LLVMCodeBuilder::createSin(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Sin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Sin, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createCos()
+CompilerValue *LLVMCodeBuilder::createCos(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Cos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Cos, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createTan()
+CompilerValue *LLVMCodeBuilder::createTan(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Tan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Tan, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createAsin()
+CompilerValue *LLVMCodeBuilder::createAsin(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Asin, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Asin, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createAcos()
+CompilerValue *LLVMCodeBuilder::createAcos(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Acos, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Acos, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createAtan()
+CompilerValue *LLVMCodeBuilder::createAtan(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Atan, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Atan, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createLn()
+CompilerValue *LLVMCodeBuilder::createLn(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Ln, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Ln, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createLog10()
+CompilerValue *LLVMCodeBuilder::createLog10(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Log10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Log10, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createExp()
+CompilerValue *LLVMCodeBuilder::createExp(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Exp, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Exp, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createExp10()
+CompilerValue *LLVMCodeBuilder::createExp10(CompilerValue *num)
 {
-    createOp(LLVMInstruction::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, 1);
+    return createOp(LLVMInstruction::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
-void LLVMCodeBuilder::createVariableWrite(Variable *variable)
+void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::WriteVariable, Compiler::StaticType::Void, Compiler::StaticType::Unknown, 1);
+    LLVMInstruction ins(LLVMInstruction::Type::WriteVariable);
     ins.workVariable = variable;
     m_variablePtrs[variable] = LLVMVariablePtr();
+    createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value });
 }
 
 void LLVMCodeBuilder::createListClear(List *list)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::ClearList, Compiler::StaticType::Void, Compiler::StaticType::Void, 0);
+    LLVMInstruction ins(LLVMInstruction::Type::ClearList);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    createOp(ins, Compiler::StaticType::Void);
 }
 
-void LLVMCodeBuilder::createListRemove(List *list)
+void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::RemoveListItem, Compiler::StaticType::Void, Compiler::StaticType::Number, 1);
+    LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index });
 }
 
-void LLVMCodeBuilder::createListAppend(List *list)
+void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::AppendToList, Compiler::StaticType::Void, Compiler::StaticType::Unknown, 1);
+    LLVMInstruction ins(LLVMInstruction::Type::AppendToList);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item });
 }
 
-void LLVMCodeBuilder::createListInsert(List *list)
+void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::InsertToList, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, 2);
+    LLVMInstruction ins(LLVMInstruction::Type::InsertToList);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
 }
 
-void LLVMCodeBuilder::createListReplace(List *list)
+void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item)
 {
-    LLVMInstruction &ins = createOp(LLVMInstruction::Type::ListReplace, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, 2);
+    LLVMInstruction ins(LLVMInstruction::Type::ListReplace);
     ins.workList = list;
     m_listPtrs[list] = LLVMListPtr();
+    createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
 }
 
-void LLVMCodeBuilder::beginIfStatement()
+void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond)
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
-    assert(!m_tmpRegs.empty());
-    ins.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
-    m_tmpRegs.pop_back();
+    ins.args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
 }
 
@@ -1197,36 +1190,30 @@ void LLVMCodeBuilder::endIf()
     m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndIf));
 }
 
-void LLVMCodeBuilder::beginRepeatLoop()
+void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count)
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatLoop);
-    assert(!m_tmpRegs.empty());
-    ins.args.push_back({ Compiler::StaticType::Number, m_tmpRegs.back() });
-    m_tmpRegs.pop_back();
+    ins.args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(count) });
     m_instructions.push_back(ins);
 }
 
-void LLVMCodeBuilder::beginWhileLoop()
+void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond)
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginWhileLoop);
-    assert(!m_tmpRegs.empty());
-    ins.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
-    m_tmpRegs.pop_back();
+    ins.args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
 }
 
-void LLVMCodeBuilder::beginRepeatUntilLoop()
+void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond)
 {
     LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatUntilLoop);
-    assert(!m_tmpRegs.empty());
-    ins.args.push_back({ Compiler::StaticType::Bool, m_tmpRegs.back() });
-    m_tmpRegs.pop_back();
+    ins.args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::beginLoopCondition()
 {
-    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::BeginLoopCondition));
+    m_instructions.push_back({ LLVMInstruction::Type::BeginLoopCondition });
 }
 
 void LLVMCodeBuilder::endLoop()
@@ -1240,13 +1227,6 @@ void LLVMCodeBuilder::endLoop()
 void LLVMCodeBuilder::yield()
 {
     m_instructions.push_back({ LLVMInstruction::Type::Yield });
-    m_currentFunction++;
-
-    assert(m_currentFunction == m_constValues.size());
-    m_constValues.push_back({});
-
-    assert(m_currentFunction == m_regs.size());
-    m_regs.push_back({});
 }
 
 void LLVMCodeBuilder::initTypes()
@@ -1376,6 +1356,12 @@ void LLVMCodeBuilder::optimize()
     modulePassManager.run(*m_module, moduleAnalysisManager);
 }
 
+CompilerValue *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg)
+{
+    m_regs.push_back(reg);
+    return reg.get();
+}
+
 void LLVMCodeBuilder::freeHeap()
 {
     // Free dynamically allocated memory
@@ -1385,19 +1371,19 @@ void LLVMCodeBuilder::freeHeap()
     m_heap.clear();
 }
 
-llvm::Value *LLVMCodeBuilder::castValue(LLVMRegisterPtr reg, Compiler::StaticType targetType)
+llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType targetType)
 {
-    if (reg->isConstValue)
-        return castConstValue(reg->constValue, targetType);
+    if (reg->isConst())
+        return castConstValue(reg->constValue(), targetType);
 
     if (reg->isRawValue)
         return castRawValue(reg, targetType);
 
-    assert(reg->type != Compiler::StaticType::Void && targetType != Compiler::StaticType::Void);
+    assert(reg->type() != Compiler::StaticType::Void && targetType != Compiler::StaticType::Void);
 
     switch (targetType) {
         case Compiler::StaticType::Number:
-            switch (reg->type) {
+            switch (reg->type()) {
                 case Compiler::StaticType::Number: {
                     // Read number directly
                     llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0);
@@ -1423,7 +1409,7 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegisterPtr reg, Compiler::StaticTyp
             }
 
         case Compiler::StaticType::Bool:
-            switch (reg->type) {
+            switch (reg->type()) {
                 case Compiler::StaticType::Number: {
                     // True if != 0
                     llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0);
@@ -1448,7 +1434,7 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegisterPtr reg, Compiler::StaticTyp
             }
 
         case Compiler::StaticType::String:
-            switch (reg->type) {
+            switch (reg->type()) {
                 case Compiler::StaticType::Number:
                 case Compiler::StaticType::Bool:
                 case Compiler::StaticType::Unknown: {
@@ -1475,14 +1461,14 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegisterPtr reg, Compiler::StaticTyp
     }
 }
 
-llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegisterPtr reg, Compiler::StaticType targetType)
+llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegister *reg, Compiler::StaticType targetType)
 {
-    if (reg->type == targetType)
+    if (reg->type() == targetType)
         return reg->value;
 
     switch (targetType) {
         case Compiler::StaticType::Number:
-            switch (reg->type) {
+            switch (reg->type()) {
                 case Compiler::StaticType::Bool:
                     // Cast bool to double
                     return m_builder.CreateUIToFP(reg->value, m_builder.getDoubleTy());
@@ -1498,7 +1484,7 @@ llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegisterPtr reg, Compiler::Static
             }
 
         case Compiler::StaticType::Bool:
-            switch (reg->type) {
+            switch (reg->type()) {
                 case Compiler::StaticType::Number:
                     // Cast double to bool (true if != 0)
                     return m_builder.CreateFCmpONE(reg->value, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)));
@@ -1513,7 +1499,7 @@ llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegisterPtr reg, Compiler::Static
             }
 
         case Compiler::StaticType::String:
-            switch (reg->type) {
+            switch (reg->type()) {
                 case Compiler::StaticType::Number: {
                     // Convert double to string
                     llvm::Value *ptr = m_builder.CreateCall(resolve_value_doubleToCString(), reg->value);
@@ -1559,12 +1545,12 @@ llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::St
     }
 }
 
-Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegisterPtr reg)
+Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg)
 {
-    Compiler::StaticType ret = reg->type;
+    Compiler::StaticType ret = reg->type();
 
     // Optimize string constants that represent numbers
-    if (reg->isConstValue && reg->type == Compiler::StaticType::String && reg->constValue.isValidNumber())
+    if (reg->isConst() && reg->type() == Compiler::StaticType::String && reg->constValue().isValidNumber())
         ret = Compiler::StaticType::Number;
 
     return ret;
@@ -1676,44 +1662,36 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr, llvm::Functi
     m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
 }
 
-LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount)
+CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
 {
     std::vector<Compiler::StaticType> types;
-    types.reserve(argCount);
+    types.reserve(args.size());
 
-    for (size_t i = 0; i < argCount; i++)
+    for (size_t i = 0; i < args.size(); i++)
         types.push_back(argType);
 
-    return createOp(type, retType, types, argCount);
+    return createOp(ins, retType, types, args);
 }
 
-LLVMInstruction &LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector<Compiler::StaticType> &argTypes, size_t argCount)
+CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
 {
-    LLVMInstruction ins(type);
-
-    assert(m_tmpRegs.size() >= argCount);
-    size_t j = 0;
-
-    for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++) {
-        ins.args.push_back({ argTypes[j], m_tmpRegs[i] });
-        j++;
-    }
+    m_instructions.push_back(ins);
+    LLVMInstruction &createdIns = m_instructions.back();
 
-    m_tmpRegs.erase(m_tmpRegs.end() - argCount, m_tmpRegs.end());
+    for (size_t i = 0; i < args.size(); i++)
+        createdIns.args.push_back({ argTypes[i], static_cast<LLVMRegister *>(args[i]) });
 
     if (retType != Compiler::StaticType::Void) {
         auto ret = std::make_shared<LLVMRegister>(retType);
         ret->isRawValue = true;
-        ins.functionReturnReg = ret;
-        m_regs[m_currentFunction].push_back(ret);
-        m_tmpRegs.push_back(ret);
+        createdIns.functionReturnReg = ret.get();
+        return addReg(ret);
     }
 
-    m_instructions.push_back(ins);
-    return m_instructions.back();
+    return nullptr;
 }
 
-void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType)
+void LLVMCodeBuilder::createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType)
 {
     llvm::Value *converted = nullptr;
 
@@ -1789,7 +1767,7 @@ void LLVMCodeBuilder::createValueStore(LLVMRegisterPtr reg, llvm::Value *targetP
     }
 }
 
-void LLVMCodeBuilder::createReusedValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType)
+void LLVMCodeBuilder::createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType)
 {
     llvm::Value *converted = nullptr;
 
@@ -1841,7 +1819,7 @@ llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Valu
     return m_builder.CreateGEP(m_valueDataType, m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
 }
 
-llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegisterPtr item, llvm::Function *func)
+llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item, llvm::Function *func)
 {
     llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
     llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_ctx, "", func);
@@ -1863,10 +1841,10 @@ llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMR
 
     // if (list[index] == item)
     m_builder.SetInsertPoint(bodyBlock);
-    LLVMRegisterPtr currentItem = std::make_shared<LLVMRegister>(listPtr.type);
-    currentItem->isRawValue = false;
-    currentItem->value = getListItem(listPtr, m_builder.CreateLoad(m_builder.getInt64Ty(), index), func);
-    llvm::Value *cmp = createComparison(currentItem, item, Comparison::EQ);
+    LLVMRegister currentItem(listPtr.type);
+    currentItem.isRawValue = false;
+    currentItem.value = getListItem(listPtr, m_builder.CreateLoad(m_builder.getInt64Ty(), index), func);
+    llvm::Value *cmp = createComparison(&currentItem, item, Comparison::EQ);
     m_builder.CreateCondBr(cmp, cmpIfBlock, cmpElseBlock);
 
     // goto nextBlock
@@ -1891,21 +1869,21 @@ llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMR
     return m_builder.CreateLoad(m_builder.getInt64Ty(), index);
 }
 
-llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
+llvm::Value *LLVMCodeBuilder::createValue(LLVMRegister *reg)
 {
-    if (reg->isConstValue) {
+    if (reg->isConst()) {
         // Create a constant ValueData instance and store it
-        llvm::Constant *value = castConstValue(reg->constValue, TYPE_MAP[reg->constValue.type()]);
+        llvm::Constant *value = castConstValue(reg->constValue(), TYPE_MAP[reg->constValue().type()]);
         llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType);
 
-        switch (reg->constValue.type()) {
+        switch (reg->constValue().type()) {
             case ValueType::Number:
                 value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0));
                 break;
 
             case ValueType::Bool:
                 // Assuming union type is int64
-                value = m_builder.getInt64(reg->constValue.toBool());
+                value = m_builder.getInt64(reg->constValue().toBool());
                 break;
 
             case ValueType::String:
@@ -1917,21 +1895,21 @@ llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
                 break;
         }
 
-        llvm::Constant *type = m_builder.getInt32(static_cast<uint32_t>(reg->constValue.type()));
+        llvm::Constant *type = m_builder.getInt32(static_cast<uint32_t>(reg->constValue().type()));
         llvm::Constant *padding = m_builder.getInt32(0);
         llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, padding, m_builder.getInt64(0) });
         m_builder.CreateStore(constValue, ret);
 
         return ret;
     } else if (reg->isRawValue) {
-        llvm::Value *value = castRawValue(reg, reg->type);
+        llvm::Value *value = castRawValue(reg, reg->type());
         llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType);
 
         // Store value
         llvm::Value *valueField = m_builder.CreateStructGEP(m_valueDataType, ret, 0);
         m_builder.CreateStore(value, valueField);
 
-        auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [&reg](const std::pair<ValueType, Compiler::StaticType> &pair) { return pair.second == reg->type; });
+        auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [&reg](const std::pair<ValueType, Compiler::StaticType> &pair) { return pair.second == reg->type(); });
 
         if (it == TYPE_MAP.end()) {
             assert(false);
@@ -1948,26 +1926,26 @@ llvm::Value *LLVMCodeBuilder::createValue(LLVMRegisterPtr reg)
         return reg->value;
 }
 
-llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type)
+llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegister *arg1, LLVMRegister *arg2, Comparison type)
 {
-    auto type1 = arg1->type;
-    auto type2 = arg2->type;
+    auto type1 = arg1->type();
+    auto type2 = arg2->type();
 
-    if (arg1->isConstValue && arg2->isConstValue) {
+    if (arg1->isConst() && arg2->isConst()) {
         // If both operands are constant, perform the comparison at compile time
         bool result = false;
 
         switch (type) {
             case Comparison::EQ:
-                result = arg1->constValue == arg2->constValue;
+                result = arg1->constValue() == arg2->constValue();
                 break;
 
             case Comparison::GT:
-                result = arg1->constValue > arg2->constValue;
+                result = arg1->constValue() > arg2->constValue();
                 break;
 
             case Comparison::LT:
-                result = arg1->constValue < arg2->constValue;
+                result = arg1->constValue() < arg2->constValue();
                 break;
 
             default:
@@ -1978,10 +1956,10 @@ llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegisterPtr arg1, LLVMRegiste
         return m_builder.getInt1(result);
     } else {
         // Optimize comparison of constant with number/bool
-        if (arg1->isConstValue && arg1->constValue.isValidNumber() && (type2 == Compiler::StaticType::Number || type2 == Compiler::StaticType::Bool))
+        if (arg1->isConst() && arg1->constValue().isValidNumber() && (type2 == Compiler::StaticType::Number || type2 == Compiler::StaticType::Bool))
             type1 = Compiler::StaticType::Number;
 
-        if (arg2->isConstValue && arg2->constValue.isValidNumber() && (type1 == Compiler::StaticType::Number || type1 == Compiler::StaticType::Bool))
+        if (arg2->isConst() && arg2->constValue().isValidNumber() && (type1 == Compiler::StaticType::Number || type1 == Compiler::StaticType::Bool))
             type2 = Compiler::StaticType::Number;
 
         // Optimize number and bool comparison
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index fa296136..cd84d1a8 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -17,6 +17,7 @@ namespace libscratchcpp
 {
 
 class Target;
+class LLVMConstantRegister;
 
 class LLVMCodeBuilder : public ICodeBuilder
 {
@@ -25,60 +26,60 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         std::shared_ptr<ExecutableCode> finalize() override;
 
-        void addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector<Compiler::StaticType> &argTypes) override;
-        void addConstValue(const Value &value) override;
-        void addVariableValue(Variable *variable) override;
-        void addListContents(List *list) override;
-        void addListItem(List *list) override;
-        void addListItemIndex(List *list) override;
-        void addListContains(List *list) override;
-        void addListSize(List *list) override;
-
-        void createAdd() override;
-        void createSub() override;
-        void createMul() override;
-        void createDiv() override;
-
-        void createCmpEQ() override;
-        void createCmpGT() override;
-        void createCmpLT() override;
-
-        void createAnd() override;
-        void createOr() override;
-        void createNot() override;
-
-        void createMod() override;
-        void createRound() override;
-        void createAbs() override;
-        void createFloor() override;
-        void createCeil() override;
-        void createSqrt() override;
-        void createSin() override;
-        void createCos() override;
-        void createTan() override;
-        void createAsin() override;
-        void createAcos() override;
-        void createAtan() override;
-        void createLn() override;
-        void createLog10() override;
-        void createExp() override;
-        void createExp10() override;
-
-        void createVariableWrite(Variable *variable) override;
+        CompilerValue *addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override;
+        CompilerConstant *addConstValue(const Value &value) override;
+        CompilerValue *addVariableValue(Variable *variable) override;
+        CompilerValue *addListContents(List *list) override;
+        CompilerValue *addListItem(List *list, CompilerValue *index) override;
+        CompilerValue *addListItemIndex(List *list, CompilerValue *item) override;
+        CompilerValue *addListContains(List *list, CompilerValue *item) override;
+        CompilerValue *addListSize(List *list) override;
+
+        CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2) override;
+        CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2) override;
+        CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) override;
+        CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) override;
+
+        CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) override;
+        CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) override;
+        CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) override;
+
+        CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2) override;
+        CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2) override;
+        CompilerValue *createNot(CompilerValue *operand) override;
+
+        CompilerValue *createMod(CompilerValue *num1, CompilerValue *num2) override;
+        CompilerValue *createRound(CompilerValue *num) override;
+        CompilerValue *createAbs(CompilerValue *num) override;
+        CompilerValue *createFloor(CompilerValue *num) override;
+        CompilerValue *createCeil(CompilerValue *num) override;
+        CompilerValue *createSqrt(CompilerValue *num) override;
+        CompilerValue *createSin(CompilerValue *num) override;
+        CompilerValue *createCos(CompilerValue *num) override;
+        CompilerValue *createTan(CompilerValue *num) override;
+        CompilerValue *createAsin(CompilerValue *num) override;
+        CompilerValue *createAcos(CompilerValue *num) override;
+        CompilerValue *createAtan(CompilerValue *num) override;
+        CompilerValue *createLn(CompilerValue *num) override;
+        CompilerValue *createLog10(CompilerValue *num) override;
+        CompilerValue *createExp(CompilerValue *num) override;
+        CompilerValue *createExp10(CompilerValue *num) override;
+
+        void createVariableWrite(Variable *variable, CompilerValue *value) override;
 
         void createListClear(List *list) override;
-        void createListRemove(List *list) override;
-        void createListAppend(List *list) override;
-        void createListInsert(List *list) override;
-        void createListReplace(List *list) override;
+        void createListRemove(List *list, CompilerValue *index) override;
+        void createListAppend(List *list, CompilerValue *item) override;
+        void createListInsert(List *list, CompilerValue *index, CompilerValue *item) override;
+        void createListReplace(List *list, CompilerValue *index, CompilerValue *item) override;
 
-        void beginIfStatement() override;
+        void beginIfStatement(CompilerValue *cond) override;
         void beginElseBranch() override;
         void endIf() override;
 
-        void beginRepeatLoop() override;
-        void beginWhileLoop() override;
-        void beginRepeatUntilLoop() override;
+        void beginRepeatLoop(CompilerValue *count) override;
+        void beginWhileLoop(CompilerValue *cond) override;
+        void beginRepeatUntilLoop(CompilerValue *cond) override;
         void beginLoopCondition() override;
         void endLoop() override;
 
@@ -101,11 +102,13 @@ class LLVMCodeBuilder : public ICodeBuilder
         void verifyFunction(llvm::Function *func);
         void optimize();
 
+        CompilerValue *addReg(std::shared_ptr<LLVMRegister> reg);
+
         void freeHeap();
-        llvm::Value *castValue(LLVMRegisterPtr reg, Compiler::StaticType targetType);
-        llvm::Value *castRawValue(LLVMRegisterPtr reg, Compiler::StaticType targetType);
+        llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType);
+        llvm::Value *castRawValue(LLVMRegister *reg, Compiler::StaticType targetType);
         llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType);
-        Compiler::StaticType optimizeRegisterType(LLVMRegisterPtr reg);
+        Compiler::StaticType optimizeRegisterType(LLVMRegister *reg);
         llvm::Type *getType(Compiler::StaticType type);
         llvm::Value *isNaN(llvm::Value *num);
         llvm::Value *removeNaN(llvm::Value *num);
@@ -117,17 +120,17 @@ class LLVMCodeBuilder : public ICodeBuilder
         void reloadLists();
         void updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func);
 
-        LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount);
-        LLVMInstruction &createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const std::vector<Compiler::StaticType> &argTypes, size_t argCount);
+        CompilerValue *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
+        CompilerValue *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
 
-        void createValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
-        void createReusedValueStore(LLVMRegisterPtr reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
+        void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
+        void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
         llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func);
-        llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegisterPtr item, llvm::Function *func);
-        llvm::Value *createValue(LLVMRegisterPtr reg);
-        llvm::Value *createComparison(LLVMRegisterPtr arg1, LLVMRegisterPtr arg2, Comparison type);
+        llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item, llvm::Function *func);
+        llvm::Value *createValue(LLVMRegister *reg);
+        llvm::Value *createComparison(LLVMRegister *arg1, LLVMRegister *arg2, Comparison type);
 
         llvm::FunctionCallee resolveFunction(const std::string name, llvm::FunctionType *type);
         llvm::FunctionCallee resolve_value_init();
@@ -175,10 +178,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::StructType *m_valueDataType = nullptr;
 
         std::vector<LLVMInstruction> m_instructions;
-        size_t m_currentFunction = 0;
-        std::vector<Value> m_constValues;
-        std::vector<std::vector<LLVMRegisterPtr>> m_regs;
-        std::vector<LLVMRegisterPtr> m_tmpRegs;
+        std::vector<std::shared_ptr<LLVMRegister>> m_regs;
         bool m_defaultWarp = false;
         bool m_warp = false;
 
diff --git a/src/dev/engine/internal/llvm/llvmconstantregister.h b/src/dev/engine/internal/llvm/llvmconstantregister.h
new file mode 100644
index 00000000..94c0d229
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmconstantregister.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/dev/compilerconstant.h>
+
+#include "llvmregister.h"
+
+namespace libscratchcpp
+{
+
+struct LLVMConstantRegister
+    : public LLVMRegisterBase
+    , public CompilerConstant
+{
+        LLVMConstantRegister(Compiler::StaticType type, const Value &value) :
+            LLVMRegisterBase(),
+            CompilerConstant(type, value)
+        {
+        }
+
+        const Value &constValue() const override { return CompilerConstant::value(); }
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index afc13d1d..d1b1dcc8 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -69,8 +69,8 @@ struct LLVMInstruction
 
         Type type;
         std::string functionName;
-        std::vector<std::pair<Compiler::StaticType, LLVMRegisterPtr>> args; // target type, register
-        LLVMRegisterPtr functionReturnReg;
+        std::vector<std::pair<Compiler::StaticType, LLVMRegister *>> args; // target type, register
+        LLVMRegister *functionReturnReg = nullptr;
         Variable *workVariable = nullptr; // for variables
         List *workList = nullptr;         // for lists
 };
diff --git a/src/dev/engine/internal/llvm/llvmregister.h b/src/dev/engine/internal/llvm/llvmregister.h
index 96e4dee3..2c7bc7f5 100644
--- a/src/dev/engine/internal/llvm/llvmregister.h
+++ b/src/dev/engine/internal/llvm/llvmregister.h
@@ -2,8 +2,7 @@
 
 #pragma once
 
-#include <scratchcpp/dev/compiler.h>
-#include <scratchcpp/value.h>
+#include "llvmregisterbase.h"
 
 namespace llvm
 {
@@ -16,19 +15,24 @@ namespace libscratchcpp
 {
 
 struct LLVMRegister
+    : public LLVMRegisterBase
+    , public CompilerValue
 {
         LLVMRegister(Compiler::StaticType type) :
-            type(type)
+            LLVMRegisterBase(),
+            CompilerValue(type)
         {
         }
 
-        Compiler::StaticType type = Compiler::StaticType::Void;
-        llvm::Value *value = nullptr;
-        bool isRawValue = false;
-        bool isConstValue = false;
-        Value constValue;
+        const Value &constValue() const override
+        {
+            if (isConst())
+                return static_cast<const CompilerConstant *>(static_cast<const CompilerValue *>(this))->value();
+            else {
+                static const Value null = Value();
+                return null;
+            }
+        }
 };
 
-using LLVMRegisterPtr = std::shared_ptr<LLVMRegister>;
-
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmregisterbase.h b/src/dev/engine/internal/llvm/llvmregisterbase.h
new file mode 100644
index 00000000..2267a6df
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmregisterbase.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/value.h>
+
+namespace llvm
+{
+
+class Value;
+
+}
+
+namespace libscratchcpp
+{
+
+struct LLVMRegisterBase
+{
+        virtual const Value &constValue() const = 0;
+
+        llvm::Value *value = nullptr;
+        bool isRawValue = false;
+};
+
+} // namespace libscratchcpp
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 5a8dd135..590bbe17 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -1003,7 +1003,11 @@ void Engine::addHatBlock(IExtension *extension, const std::string &opcode)
     if (m_compileFunctions.find(extension) == m_compileFunctions.cend())
         m_compileFunctions[extension] = {};
 
+#ifdef USE_LLVM
+    m_compileFunctions[extension][opcode] = [](Compiler *compiler) -> CompilerValue * { return nullptr; };
+#else
     m_compileFunctions[extension][opcode] = [](Compiler *compiler) {};
+#endif
 }
 
 void Engine::addInput(IExtension *extension, const std::string &name, int id)
diff --git a/src/scratch/block.cpp b/src/scratch/block.cpp
index 0350ccd4..8e014bc1 100644
--- a/src/scratch/block.cpp
+++ b/src/scratch/block.cpp
@@ -4,6 +4,10 @@
 #include <scratchcpp/input.h>
 #include <scratchcpp/field.h>
 #include <scratchcpp/comment.h>
+#ifdef USE_LLVM
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#endif
 #include <iostream>
 
 #include "block_p.h"
@@ -17,12 +21,23 @@ Block::Block(const std::string &id, const std::string &opcode) :
 {
 }
 
+#ifdef USE_LLVM
+/*! Calls the compile function. */
+CompilerValue *Block::compile(Compiler *compiler)
+{
+    if (impl->compileFunction)
+        return impl->compileFunction(compiler);
+    else
+        return nullptr;
+}
+#else
 /*! Calls the compile function. */
 void Block::compile(Compiler *compiler)
 {
     if (impl->compileFunction)
         return impl->compileFunction(compiler);
 }
+#endif
 
 /*! Returns the opcode. */
 const std::string &Block::opcode() const
@@ -43,7 +58,7 @@ void Block::setCompileFunction(BlockComp newCompileFunction)
 }
 
 /*! Returns the edge-activated hat predicate compile function. \see <a href="blockSections.html">Block sections</a> */
-BlockComp Block::hatPredicateCompileFunction() const
+HatPredicateCompileFunc Block::hatPredicateCompileFunction() const
 {
     return impl->hatPredicateCompileFunction;
 }
diff --git a/src/scratch/inputvalue.cpp b/src/scratch/inputvalue.cpp
index 2d66269d..03edf80a 100644
--- a/src/scratch/inputvalue.cpp
+++ b/src/scratch/inputvalue.cpp
@@ -3,6 +3,7 @@
 #include <scratchcpp/inputvalue.h>
 #ifdef USE_LLVM
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
 #else
 #include <scratchcpp/compiler.h>
 #endif
@@ -26,55 +27,68 @@ InputValue::InputValue(Type type) :
 {
 }
 
+#ifdef USE_LLVM
 /*! Compiles the input value. */
-void InputValue::compile(Compiler *compiler)
+CompilerValue *InputValue::compile(Compiler *compiler)
 {
     switch (impl->type) {
         case Type::Color:
             // TODO: Add support for colors
-            break;
+            return nullptr;
 
         case Type::Variable: {
             assert(impl->valuePtr);
-#ifdef USE_LLVM
             Variable *var = dynamic_cast<Variable *>(impl->valuePtr.get());
 
             if (var)
-                compiler->addVariableValue(var);
+                return compiler->addVariableValue(var);
             else
-                compiler->addConstValue(Value());
-#else
-            compiler->addInstruction(vm::OP_READ_VAR, { compiler->variableIndex(impl->valuePtr) });
-#endif
-
-            break;
+                return compiler->addConstValue(Value());
         }
 
         case Type::List: {
             assert(impl->valuePtr);
-#ifdef USE_LLVM
             List *list = dynamic_cast<List *>(impl->valuePtr.get());
 
             if (list)
-                compiler->addListContents(list);
+                return compiler->addListContents(list);
             else
-                compiler->addConstValue(Value());
+                return compiler->addConstValue(Value());
+        }
+
+        default:
+            return compiler->addConstValue(impl->value);
+    }
+}
 #else
+/*! Compiles the input value. */
+void InputValue::compile(Compiler *compiler)
+{
+    switch (impl->type) {
+        case Type::Color:
+            // TODO: Add support for colors
+            break;
+
+        case Type::Variable: {
+            assert(impl->valuePtr);
+            compiler->addInstruction(vm::OP_READ_VAR, { compiler->variableIndex(impl->valuePtr) });
+
+            break;
+        }
+
+        case Type::List: {
+            assert(impl->valuePtr);
             compiler->addInstruction(vm::OP_READ_LIST, { compiler->listIndex(impl->valuePtr) });
-#endif
 
             break;
         }
 
         default:
-#ifdef USE_LLVM
-            compiler->addConstValue(impl->value);
-#else
             compiler->addInstruction(vm::OP_CONST, { compiler->constIndex(this) });
-#endif
             break;
     }
 }
+#endif // USE_LLVM
 
 /*! Returns the type of the value. */
 InputValue::Type InputValue::type() const
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 9042a515..8eb81046 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -1,4 +1,5 @@
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
@@ -68,15 +69,21 @@ TEST_F(CompilerTest, AddFunctionCall)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
     m_compareBlock = block;
-    block->setCompileFunction([](Compiler *compiler) {
-        ASSERT_EQ(compiler->block(), m_compareBlock);
-        std::vector<Compiler::StaticType> args = { Compiler::StaticType::Number, Compiler::StaticType::Bool };
-        EXPECT_CALL(*m_builder, addFunctionCall("test1", Compiler::StaticType::Void, args));
-        compiler->addFunctionCall("test1", Compiler::StaticType::Void, args);
-
-        args = { Compiler::StaticType::String };
-        EXPECT_CALL(*m_builder, addFunctionCall("test2", Compiler::StaticType::Bool, args));
-        compiler->addFunctionCall("test2", Compiler::StaticType::Bool, args);
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_EQ(compiler->block(), m_compareBlock);
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        Compiler::ArgTypes argTypes = { Compiler::StaticType::Number, Compiler::StaticType::Bool };
+        Compiler::Args args = { &arg1, &arg2 };
+        EXPECT_CALL(*m_builder, addFunctionCall("test1", Compiler::StaticType::Void, argTypes, args));
+        compiler->addFunctionCall("test1", Compiler::StaticType::Void, argTypes, args);
+
+        args = { &arg1 };
+        argTypes = { Compiler::StaticType::String };
+        EXPECT_CALL(*m_builder, addFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args));
+        compiler->addFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -86,15 +93,19 @@ TEST_F(CompilerTest, AddConstValue)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, addConstValue(Value(1)));
-        compiler->addConstValue(1);
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerConstant ret(Compiler::StaticType::Unknown, Value());
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("test")));
+        EXPECT_CALL(*m_builder, addConstValue(Value(1))).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addConstValue(1), &ret);
+
+        EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&ret));
         compiler->addConstValue("test");
 
-        EXPECT_CALL(*m_builder, addConstValue(Value(3.5)));
-        compiler->addConstValue(3.5);
+        EXPECT_CALL(*m_builder, addConstValue(Value(3.5))).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addConstValue(3.5), nullptr);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -104,13 +115,17 @@ TEST_F(CompilerTest, AddVariableValue)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
         Variable var1("", ""), var2("", "");
-        EXPECT_CALL(*m_builder, addVariableValue(&var1));
-        compiler->addVariableValue(&var1);
 
-        EXPECT_CALL(*m_builder, addVariableValue(&var2));
-        compiler->addVariableValue(&var2);
+        EXPECT_CALL(*m_builder, addVariableValue(&var1)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addVariableValue(&var1), &ret);
+
+        EXPECT_CALL(*m_builder, addVariableValue(&var2)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addVariableValue(&var2), nullptr);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -120,13 +135,17 @@ TEST_F(CompilerTest, AddListContents)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
         List list1("", ""), list2("", "");
-        EXPECT_CALL(*m_builder, addListContents(&list1));
-        compiler->addListContents(&list1);
 
-        EXPECT_CALL(*m_builder, addListContents(&list2));
-        compiler->addListContents(&list2);
+        EXPECT_CALL(*m_builder, addListContents(&list1)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addListContents(&list1), &ret);
+
+        EXPECT_CALL(*m_builder, addListContents(&list2)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addListContents(&list2), nullptr);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -136,13 +155,18 @@ TEST_F(CompilerTest, AddListItem)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        CompilerValue arg(Compiler::StaticType::Unknown);
         List list1("", ""), list2("", "");
-        EXPECT_CALL(*m_builder, addListItem(&list1));
-        compiler->addListItem(&list1);
 
-        EXPECT_CALL(*m_builder, addListItem(&list2));
-        compiler->addListItem(&list2);
+        EXPECT_CALL(*m_builder, addListItem(&list1, &arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addListItem(&list1, &arg), &ret);
+
+        EXPECT_CALL(*m_builder, addListItem(&list2, &arg)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addListItem(&list2, &arg), nullptr);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -152,13 +176,18 @@ TEST_F(CompilerTest, AddListItemIndex)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        CompilerValue arg(Compiler::StaticType::Unknown);
         List list1("", ""), list2("", "");
-        EXPECT_CALL(*m_builder, addListItemIndex(&list1));
-        compiler->addListItemIndex(&list1);
 
-        EXPECT_CALL(*m_builder, addListItemIndex(&list2));
-        compiler->addListItemIndex(&list2);
+        EXPECT_CALL(*m_builder, addListItemIndex(&list1, &arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addListItemIndex(&list1, &arg), &ret);
+
+        EXPECT_CALL(*m_builder, addListItemIndex(&list2, &arg)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addListItemIndex(&list2, &arg), nullptr);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -168,13 +197,17 @@ TEST_F(CompilerTest, AddListSize)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
         List list1("", ""), list2("", "");
-        EXPECT_CALL(*m_builder, addListSize(&list1));
-        compiler->addListSize(&list1);
 
-        EXPECT_CALL(*m_builder, addListSize(&list2));
-        compiler->addListSize(&list2);
+        EXPECT_CALL(*m_builder, addListSize(&list1)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addListSize(&list1), &ret);
+
+        EXPECT_CALL(*m_builder, addListSize(&list2)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addListSize(&list2), nullptr);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -184,13 +217,18 @@ TEST_F(CompilerTest, AddListContains)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        CompilerValue arg(Compiler::StaticType::Unknown);
         List list1("", ""), list2("", "");
-        EXPECT_CALL(*m_builder, addListContains(&list1));
-        compiler->addListContains(&list1);
 
-        EXPECT_CALL(*m_builder, addListContains(&list2));
-        compiler->addListContains(&list2);
+        EXPECT_CALL(*m_builder, addListContains(&list1, &arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addListContains(&list1, &arg), &ret);
+
+        EXPECT_CALL(*m_builder, addListContains(&list2, &arg)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addListContains(&list2, &arg), nullptr);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -202,9 +240,9 @@ TEST_F(CompilerTest, AddInput)
     auto block = std::make_shared<Block>("a", "");
     auto valueBlock = std::make_shared<Block>("b", "");
     m_compareBlock = valueBlock;
-    valueBlock->setCompileFunction([](Compiler *compiler) {
-        ASSERT_EQ(compiler->block(), m_compareBlock);
-        compiler->addConstValue("value block");
+    valueBlock->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_EQ(compiler->block(), m_compareBlock);
+        return compiler->addConstValue("value block");
     });
 
     auto menu = std::make_shared<Block>("c", "");
@@ -248,36 +286,40 @@ TEST_F(CompilerTest, AddInput)
     obscuredShadowBlock->setValueBlock(valueBlock);
     block->addInput(obscuredShadowBlock);
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, addConstValue(Value()));
-        compiler->addInput("INVALID");
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerConstant constRet(Compiler::StaticType::Unknown, Value());
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, addConstValue(Value())).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("INVALID"), &constRet);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("test")));
-        compiler->addInput("SHADOW");
+        EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("SHADOW"), &constRet);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("value block")));
-        compiler->addInput("SHADOW_BLOCK");
+        EXPECT_CALL(*m_builder, addConstValue(Value("value block"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("SHADOW_BLOCK"), &constRet);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("selected item")));
-        compiler->addInput("SHADOW_MENU");
+        EXPECT_CALL(*m_builder, addConstValue(Value("selected item"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("SHADOW_MENU"), &constRet);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("test")));
-        compiler->addInput("NO_SHADOW");
+        EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("NO_SHADOW"), &constRet);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("value block")));
-        compiler->addInput("NO_SHADOW_BLOCK");
+        EXPECT_CALL(*m_builder, addConstValue(Value("value block"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("NO_SHADOW_BLOCK"), &constRet);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("selected item")));
-        compiler->addInput("NO_SHADOW_MENU");
+        EXPECT_CALL(*m_builder, addConstValue(Value("selected item"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("NO_SHADOW_MENU"), &constRet);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("test")));
-        compiler->addInput("OBSCURED_SHADOW");
+        EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("OBSCURED_SHADOW"), &constRet);
 
-        EXPECT_CALL(*m_builder, addVariableValue(m_testVar.get()));
-        compiler->addInput("OBSCURED_SHADOW_VARIABLE");
+        EXPECT_CALL(*m_builder, addVariableValue(m_testVar.get())).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addInput("OBSCURED_SHADOW_VARIABLE"), &ret);
 
-        EXPECT_CALL(*m_builder, addConstValue(Value("value block")));
-        compiler->addInput("OBSCURED_SHADOW_BLOCK");
+        EXPECT_CALL(*m_builder, addConstValue(Value("value block"))).WillOnce(Return(&constRet));
+        EXPECT_EQ(compiler->addInput("OBSCURED_SHADOW_BLOCK"), &constRet);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -288,9 +330,14 @@ TEST_F(CompilerTest, CreateAdd)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createAdd);
-        compiler->createAdd();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createAdd(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createAdd(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -301,9 +348,14 @@ TEST_F(CompilerTest, CreateSub)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createSub);
-        compiler->createSub();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createSub(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createSub(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -314,9 +366,14 @@ TEST_F(CompilerTest, CreateMul)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createMul);
-        compiler->createMul();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createMul(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createMul(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -327,9 +384,14 @@ TEST_F(CompilerTest, CreateDiv)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createDiv);
-        compiler->createDiv();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createDiv(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createDiv(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -340,9 +402,14 @@ TEST_F(CompilerTest, CreateCmpEQ)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createCmpEQ);
-        compiler->createCmpEQ();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createCmpEQ(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createCmpEQ(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -353,9 +420,14 @@ TEST_F(CompilerTest, CreateCmpGT)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createCmpGT);
-        compiler->createCmpGT();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createCmpGT(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createCmpGT(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -366,9 +438,14 @@ TEST_F(CompilerTest, CreateCmpLT)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createCmpLT);
-        compiler->createCmpLT();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createCmpLT(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createCmpLT(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -379,9 +456,14 @@ TEST_F(CompilerTest, CreateAnd)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createAnd);
-        compiler->createAnd();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createAnd(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createAnd(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -392,9 +474,14 @@ TEST_F(CompilerTest, CreateOr)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createOr);
-        compiler->createOr();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createOr(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createOr(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -405,9 +492,13 @@ TEST_F(CompilerTest, CreateNot)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createNot);
-        compiler->createNot();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createNot(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createNot(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -418,9 +509,14 @@ TEST_F(CompilerTest, CreateMod)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createMod);
-        compiler->createMod();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createMod(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createMod(&arg1, &arg2), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -431,9 +527,13 @@ TEST_F(CompilerTest, CreateRound)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createRound);
-        compiler->createRound();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createRound(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createRound(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -444,9 +544,13 @@ TEST_F(CompilerTest, CreateAbs)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createAbs);
-        compiler->createAbs();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createAbs(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createAbs(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -457,9 +561,13 @@ TEST_F(CompilerTest, CreateFloor)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createFloor);
-        compiler->createFloor();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createFloor(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createFloor(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -470,9 +578,13 @@ TEST_F(CompilerTest, CreateCeil)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createCeil);
-        compiler->createCeil();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createCeil(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createCeil(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -483,9 +595,13 @@ TEST_F(CompilerTest, CreateSqrt)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createSqrt);
-        compiler->createSqrt();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createSqrt(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createSqrt(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -496,9 +612,13 @@ TEST_F(CompilerTest, CreateSin)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createSin);
-        compiler->createSin();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createSin(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createSin(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -509,9 +629,13 @@ TEST_F(CompilerTest, CreateCos)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createCos);
-        compiler->createCos();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createCos(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createCos(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -522,9 +646,13 @@ TEST_F(CompilerTest, CreateTan)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createTan);
-        compiler->createTan();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createTan(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createTan(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -535,9 +663,13 @@ TEST_F(CompilerTest, CreateAsin)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createAsin);
-        compiler->createAsin();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createAsin(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createAsin(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -548,9 +680,13 @@ TEST_F(CompilerTest, CreateAcos)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createAcos);
-        compiler->createAcos();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createAcos(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createAcos(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -561,9 +697,13 @@ TEST_F(CompilerTest, CreateAtan)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createAtan);
-        compiler->createAtan();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createAtan(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createAtan(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -574,9 +714,13 @@ TEST_F(CompilerTest, CreateLn)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createLn);
-        compiler->createLn();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createLn(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createLn(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -587,9 +731,13 @@ TEST_F(CompilerTest, CreateLog10)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createLog10);
-        compiler->createLog10();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createLog10(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createLog10(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -600,9 +748,13 @@ TEST_F(CompilerTest, CreateExp)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createExp);
-        compiler->createExp();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createExp(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createExp(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -613,9 +765,13 @@ TEST_F(CompilerTest, CreateExp10)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, createExp10);
-        compiler->createExp10();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createExp10(&arg)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createExp10(&arg), &ret);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -626,10 +782,13 @@ TEST_F(CompilerTest, CreateVariableWrite)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         auto var = std::make_shared<Variable>("", "");
-        EXPECT_CALL(*m_builder, createVariableWrite(var.get()));
-        compiler->createVariableWrite(var.get());
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createVariableWrite(var.get(), &arg));
+        compiler->createVariableWrite(var.get(), &arg);
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -640,18 +799,21 @@ TEST_F(CompilerTest, CustomIfStatement)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
-    block->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginIfStatement());
-        compiler->beginIfStatement();
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginIfStatement(&arg));
+        compiler->beginIfStatement(&arg);
         EXPECT_CALL(*m_builder, endIf());
         compiler->endIf();
 
-        EXPECT_CALL(*m_builder, beginIfStatement());
-        compiler->beginIfStatement();
+        EXPECT_CALL(*m_builder, beginIfStatement(&arg));
+        compiler->beginIfStatement(&arg);
         EXPECT_CALL(*m_builder, beginElseBranch());
         compiler->beginElseBranch();
         EXPECT_CALL(*m_builder, endIf());
         compiler->endIf();
+
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -663,25 +825,29 @@ TEST_F(CompilerTest, MoveToIf)
     EXPECT_CALL(*m_builder, beginElseBranch).Times(0);
 
     auto if1 = std::make_shared<Block>("", "if");
-    if1->setCompileFunction([](Compiler *compiler) {
+    if1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
         EXPECT_CALL(*m_builder, beginIfStatement).Times(0);
         EXPECT_CALL(*m_builder, endIf).Times(0);
-        compiler->moveToIf(nullptr);
+        compiler->moveToIf(&arg, nullptr);
+        return nullptr;
     });
 
     auto if2 = std::make_shared<Block>("", "if");
     if1->setNext(if2);
     if2->setParent(if1);
-    if2->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginIfStatement());
+    if2->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginIfStatement(&arg));
         EXPECT_CALL(*m_builder, addConstValue(Value()));
         EXPECT_CALL(*m_builder, endIf());
-        compiler->moveToIf(compiler->input("SUBSTACK")->valueBlock());
+        compiler->moveToIf(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
     });
 
     auto substack1 = std::make_shared<Block>("", "substack");
     substack1->setParent(if2);
-    substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(Value()); });
+    substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
 
     auto input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack1);
@@ -695,32 +861,36 @@ TEST_F(CompilerTest, MoveToIfElse)
     Compiler compiler(&m_engine, &m_target);
 
     auto if1 = std::make_shared<Block>("", "if");
-    if1->setCompileFunction([](Compiler *compiler) {
+    if1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
         EXPECT_CALL(*m_builder, beginIfStatement).Times(0);
         EXPECT_CALL(*m_builder, beginElseBranch).Times(0);
         EXPECT_CALL(*m_builder, endIf).Times(0);
-        compiler->moveToIfElse(nullptr, nullptr);
+        compiler->moveToIfElse(&arg, nullptr, nullptr);
+        return nullptr;
     });
 
     auto if2 = std::make_shared<Block>("", "if");
     if1->setNext(if2);
     if2->setParent(if1);
-    if2->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginIfStatement());
+    if2->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginIfStatement(&arg));
         EXPECT_CALL(*m_builder, addConstValue(Value(1)));
         EXPECT_CALL(*m_builder, beginElseBranch());
         EXPECT_CALL(*m_builder, addConstValue(Value(2)));
         EXPECT_CALL(*m_builder, endIf());
-        compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock());
+        compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock());
+        return nullptr;
     });
 
     auto substack1 = std::make_shared<Block>("", "substack");
     substack1->setParent(if2);
-    substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); });
+    substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); });
 
     auto substack2 = std::make_shared<Block>("", "substack");
     substack2->setParent(if2);
-    substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); });
+    substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); });
 
     auto input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack1);
@@ -733,29 +903,35 @@ TEST_F(CompilerTest, MoveToIfElse)
     auto if3 = std::make_shared<Block>("", "if");
     if2->setNext(if3);
     if3->setParent(if2);
-    if3->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginIfStatement()).Times(3);
+    if3->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginIfStatement).Times(3);
         EXPECT_CALL(*m_builder, beginElseBranch()).Times(3);
         EXPECT_CALL(*m_builder, endIf()).Times(3);
         EXPECT_CALL(*m_builder, addConstValue(Value(1)));
         EXPECT_CALL(*m_builder, addConstValue(Value(2)));
         EXPECT_CALL(*m_builder, addConstValue(Value(3)));
         EXPECT_CALL(*m_builder, addConstValue(Value(4)));
-        compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock());
+        compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock());
+        return nullptr;
     });
 
     // if
     auto ifSubstack1 = std::make_shared<Block>("", "if");
     ifSubstack1->setParent(if3);
-    ifSubstack1->setCompileFunction([](Compiler *compiler) { compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); });
+    ifSubstack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock());
+        return nullptr;
+    });
 
     substack1 = std::make_shared<Block>("", "substack");
     substack1->setParent(ifSubstack1);
-    substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); });
+    substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); });
 
     substack2 = std::make_shared<Block>("", "substack");
     substack2->setParent(ifSubstack1);
-    substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); });
+    substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); });
 
     input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack1);
@@ -767,15 +943,19 @@ TEST_F(CompilerTest, MoveToIfElse)
     // else
     auto ifSubstack2 = std::make_shared<Block>("", "if");
     ifSubstack2->setParent(if3);
-    ifSubstack2->setCompileFunction([](Compiler *compiler) { compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); });
+    ifSubstack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock());
+        return nullptr;
+    });
 
     substack1 = std::make_shared<Block>("", "substack");
     substack1->setParent(ifSubstack2);
-    substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(3); });
+    substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(3); });
 
     substack2 = std::make_shared<Block>("", "substack");
     substack2->setParent(ifSubstack2);
-    substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(4); });
+    substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(4); });
 
     input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack1);
@@ -796,17 +976,19 @@ TEST_F(CompilerTest, MoveToIfElse)
     auto if4 = std::make_shared<Block>("", "if");
     if3->setNext(if4);
     if4->setParent(if3);
-    if4->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginIfStatement());
+    if4->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginIfStatement(&arg));
         EXPECT_CALL(*m_builder, beginElseBranch());
         EXPECT_CALL(*m_builder, addConstValue(Value(2)));
         EXPECT_CALL(*m_builder, endIf());
-        compiler->moveToIfElse(nullptr, compiler->input("SUBSTACK2")->valueBlock());
+        compiler->moveToIfElse(&arg, nullptr, compiler->input("SUBSTACK2")->valueBlock());
+        return nullptr;
     });
 
     substack2 = std::make_shared<Block>("", "substack");
     substack2->setParent(if4);
-    substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); });
+    substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); });
 
     input = std::make_shared<Input>("SUBSTACK2", Input::Type::NoShadow);
     input->setValueBlock(substack2);
@@ -816,17 +998,19 @@ TEST_F(CompilerTest, MoveToIfElse)
     auto if5 = std::make_shared<Block>("", "if");
     if4->setNext(if5);
     if5->setParent(if4);
-    if5->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginIfStatement());
+    if5->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginIfStatement(&arg));
         EXPECT_CALL(*m_builder, addConstValue(Value(1)));
         EXPECT_CALL(*m_builder, beginElseBranch()).Times(0);
         EXPECT_CALL(*m_builder, endIf());
-        compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), nullptr);
+        compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), nullptr);
+        return nullptr;
     });
 
     substack1 = std::make_shared<Block>("", "substack");
     substack1->setParent(if5);
-    substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); });
+    substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); });
 
     input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack1);
@@ -836,7 +1020,7 @@ TEST_F(CompilerTest, MoveToIfElse)
     auto block = std::make_shared<Block>("", "");
     block->setParent(if5);
     if5->setNext(block);
-    block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); });
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
     compile(compiler, if1);
@@ -847,25 +1031,29 @@ TEST_F(CompilerTest, MoveToRepeatLoop)
     Compiler compiler(&m_engine, &m_target);
 
     auto l1 = std::make_shared<Block>("", "loop");
-    l1->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatLoop());
+    l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatLoop(&arg));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToRepeatLoop(nullptr);
+        compiler->moveToRepeatLoop(&arg, nullptr);
+        return nullptr;
     });
 
     auto l2 = std::make_shared<Block>("", "loop");
     l1->setNext(l2);
     l2->setParent(l1);
-    l2->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatLoop());
+    l2->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatLoop(&arg));
         EXPECT_CALL(*m_builder, addConstValue(Value(2)));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock());
+        compiler->moveToRepeatLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
     });
 
     auto substack = std::make_shared<Block>("", "substack");
     substack->setParent(l2);
-    substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); });
+    substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); });
 
     auto input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack);
@@ -875,21 +1063,27 @@ TEST_F(CompilerTest, MoveToRepeatLoop)
     auto l3 = std::make_shared<Block>("", "loop");
     l2->setNext(l3);
     l3->setParent(l2);
-    l3->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatLoop()).Times(2);
+    l3->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatLoop).Times(2);
         EXPECT_CALL(*m_builder, endLoop()).Times(2);
         EXPECT_CALL(*m_builder, addConstValue(Value(1)));
-        compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock());
+        compiler->moveToRepeatLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
     });
 
     // Begin loop
     auto loopSubstack = std::make_shared<Block>("", "loop");
     loopSubstack->setParent(l3);
-    loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock()); });
+    loopSubstack->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        compiler->moveToRepeatLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
+    });
 
     substack = std::make_shared<Block>("", "substack");
     substack->setParent(loopSubstack);
-    substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); });
+    substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); });
 
     input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack);
@@ -904,17 +1098,19 @@ TEST_F(CompilerTest, MoveToRepeatLoop)
     auto l4 = std::make_shared<Block>("", "loop");
     l3->setNext(l4);
     l4->setParent(l3);
-    l4->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatLoop());
+    l4->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatLoop(&arg));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToRepeatLoop(nullptr);
+        compiler->moveToRepeatLoop(&arg, nullptr);
+        return nullptr;
     });
 
     // Code after the loop
     auto block = std::make_shared<Block>("", "");
     block->setParent(l4);
     l4->setNext(block);
-    block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); });
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
     compile(compiler, l1);
@@ -925,25 +1121,29 @@ TEST_F(CompilerTest, MoveToWhileLoop)
     Compiler compiler(&m_engine, &m_target);
 
     auto l1 = std::make_shared<Block>("", "loop");
-    l1->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginWhileLoop());
+    l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginWhileLoop(&arg));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToWhileLoop(nullptr);
+        compiler->moveToWhileLoop(&arg, nullptr);
+        return nullptr;
     });
 
     auto l2 = std::make_shared<Block>("", "loop");
     l1->setNext(l2);
     l2->setParent(l1);
-    l2->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginWhileLoop());
+    l2->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginWhileLoop(&arg));
         EXPECT_CALL(*m_builder, addConstValue(Value(2)));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock());
+        compiler->moveToWhileLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
     });
 
     auto substack = std::make_shared<Block>("", "substack");
     substack->setParent(l2);
-    substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); });
+    substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); });
 
     auto input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack);
@@ -953,21 +1153,27 @@ TEST_F(CompilerTest, MoveToWhileLoop)
     auto l3 = std::make_shared<Block>("", "loop");
     l2->setNext(l3);
     l3->setParent(l2);
-    l3->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginWhileLoop()).Times(2);
+    l3->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginWhileLoop).Times(2);
         EXPECT_CALL(*m_builder, endLoop()).Times(2);
         EXPECT_CALL(*m_builder, addConstValue(Value(1)));
-        compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock());
+        compiler->moveToWhileLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
     });
 
     // Begin loop
     auto loopSubstack = std::make_shared<Block>("", "loop");
     loopSubstack->setParent(l3);
-    loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock()); });
+    loopSubstack->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        compiler->moveToWhileLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
+    });
 
     substack = std::make_shared<Block>("", "substack");
     substack->setParent(loopSubstack);
-    substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); });
+    substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); });
 
     input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack);
@@ -982,17 +1188,19 @@ TEST_F(CompilerTest, MoveToWhileLoop)
     auto l4 = std::make_shared<Block>("", "loop");
     l3->setNext(l4);
     l4->setParent(l3);
-    l4->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginWhileLoop());
+    l4->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginWhileLoop(&arg));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToWhileLoop(nullptr);
+        compiler->moveToWhileLoop(&arg, nullptr);
+        return nullptr;
     });
 
     // Code after the loop
     auto block = std::make_shared<Block>("", "");
     block->setParent(l4);
     l4->setNext(block);
-    block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); });
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
     compile(compiler, l1);
@@ -1003,25 +1211,29 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop)
     Compiler compiler(&m_engine, &m_target);
 
     auto l1 = std::make_shared<Block>("", "loop");
-    l1->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatUntilLoop());
+    l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToRepeatUntilLoop(nullptr);
+        compiler->moveToRepeatUntilLoop(&arg, nullptr);
+        return nullptr;
     });
 
     auto l2 = std::make_shared<Block>("", "loop");
     l1->setNext(l2);
     l2->setParent(l1);
-    l2->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatUntilLoop());
+    l2->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg));
         EXPECT_CALL(*m_builder, addConstValue(Value(2)));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock());
+        compiler->moveToRepeatUntilLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
     });
 
     auto substack = std::make_shared<Block>("", "substack");
     substack->setParent(l2);
-    substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); });
+    substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); });
 
     auto input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack);
@@ -1031,21 +1243,27 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop)
     auto l3 = std::make_shared<Block>("", "loop");
     l2->setNext(l3);
     l3->setParent(l2);
-    l3->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatUntilLoop()).Times(2);
+    l3->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatUntilLoop).Times(2);
         EXPECT_CALL(*m_builder, endLoop()).Times(2);
         EXPECT_CALL(*m_builder, addConstValue(Value(1)));
-        compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock());
+        compiler->moveToRepeatUntilLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
     });
 
     // Begin loop
     auto loopSubstack = std::make_shared<Block>("", "loop");
     loopSubstack->setParent(l3);
-    loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock()); });
+    loopSubstack->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        compiler->moveToRepeatUntilLoop(&arg, compiler->input("SUBSTACK")->valueBlock());
+        return nullptr;
+    });
 
     substack = std::make_shared<Block>("", "substack");
     substack->setParent(loopSubstack);
-    substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); });
+    substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); });
 
     input = std::make_shared<Input>("SUBSTACK", Input::Type::NoShadow);
     input->setValueBlock(substack);
@@ -1060,17 +1278,19 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop)
     auto l4 = std::make_shared<Block>("", "loop");
     l3->setNext(l4);
     l4->setParent(l3);
-    l4->setCompileFunction([](Compiler *compiler) {
-        EXPECT_CALL(*m_builder, beginRepeatUntilLoop());
+    l4->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg));
         EXPECT_CALL(*m_builder, endLoop());
-        compiler->moveToRepeatUntilLoop(nullptr);
+        compiler->moveToRepeatUntilLoop(&arg, nullptr);
+        return nullptr;
     });
 
     // Code after the loop
     auto block = std::make_shared<Block>("", "");
     block->setParent(l4);
     l4->setNext(block);
-    block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); });
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
     compile(compiler, l1);
@@ -1080,9 +1300,10 @@ TEST_F(CompilerTest, BeginLoopCondition)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) {
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         EXPECT_CALL(*m_builder, beginLoopCondition());
         compiler->beginLoopCondition();
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -1093,9 +1314,10 @@ TEST_F(CompilerTest, Input)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
     block->addInput(std::make_shared<Input>("TEST", Input::Type::Shadow));
-    block->setCompileFunction([](Compiler *compiler) {
-        ASSERT_EQ(compiler->input("INVALID"), nullptr);
-        ASSERT_EQ(compiler->input("TEST"), compiler->block()->inputAt(0).get());
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_EQ(compiler->input("INVALID"), nullptr);
+        EXPECT_EQ(compiler->input("TEST"), compiler->block()->inputAt(0).get());
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -1106,9 +1328,10 @@ TEST_F(CompilerTest, Field)
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("a", "");
     block->addField(std::make_shared<Field>("TEST", "test"));
-    block->setCompileFunction([](Compiler *compiler) {
-        ASSERT_EQ(compiler->field("INVALID"), nullptr);
-        ASSERT_EQ(compiler->field("TEST"), compiler->block()->fieldAt(0).get());
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_EQ(compiler->field("INVALID"), nullptr);
+        EXPECT_EQ(compiler->field("TEST"), compiler->block()->fieldAt(0).get());
+        return nullptr;
     });
 
     compile(compiler, block);
@@ -1118,15 +1341,15 @@ TEST_F(CompilerTest, UnsupportedBlocks)
 {
     auto valueBlock1 = std::make_shared<Block>("v1", "value_block1");
     auto valueBlock2 = std::make_shared<Block>("v2", "value_block2");
-    valueBlock2->setCompileFunction([](Compiler *) {});
+    valueBlock2->setCompileFunction([](Compiler *) -> CompilerValue * { return nullptr; });
 
     auto valueBlock3 = std::make_shared<Block>("v3", "value_block3");
     auto valueBlock4 = std::make_shared<Block>("v4", "value_block4");
-    valueBlock4->setCompileFunction([](Compiler *) {});
+    valueBlock4->setCompileFunction([](Compiler *) -> CompilerValue * { return nullptr; });
 
     auto valueBlock5 = std::make_shared<Block>("v5", "value_block5");
     auto valueBlock6 = std::make_shared<Block>("v6", "value_block6");
-    valueBlock6->setCompileFunction([](Compiler *) {});
+    valueBlock6->setCompileFunction([](Compiler *) -> CompilerValue * { return nullptr; });
 
     auto block1 = std::make_shared<Block>("b1", "block1");
 
@@ -1153,13 +1376,14 @@ TEST_F(CompilerTest, UnsupportedBlocks)
     auto input6 = std::make_shared<Input>("INPUT6", Input::Type::ObscuredShadow);
     input6->setValueBlock(valueBlock6);
     block3->addInput(input6);
-    block3->setCompileFunction([](Compiler *compiler) {
+    block3->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         compiler->addInput("INPUT1");
         compiler->addInput("INPUT2");
         compiler->addInput("INPUT3");
         compiler->addInput("INPUT4");
         compiler->addInput("INPUT5");
         compiler->addInput("INPUT6");
+        return nullptr;
     });
     block3->setParent(block2);
     block2->setNext(block3);
@@ -1169,7 +1393,7 @@ TEST_F(CompilerTest, UnsupportedBlocks)
     block3->setNext(block4);
 
     Compiler compiler(&m_engine, &m_target);
-    EXPECT_CALL(*m_builder, addConstValue).WillRepeatedly(Return());
+    EXPECT_CALL(*m_builder, addConstValue).WillRepeatedly(Return(nullptr));
     compile(compiler, block1);
 
     ASSERT_EQ(compiler.unsupportedBlocks(), std::unordered_set<std::string>({ "block1", "block2", "value_block1", "value_block3", "value_block5", "block4" }));
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 747a63f5..07746d3d 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -19,6 +19,37 @@ using ::testing::Eq;
 class LLVMCodeBuilderTest : public testing::Test
 {
     public:
+        enum class OpType
+        {
+            Add,
+            Sub,
+            Mul,
+            Div,
+            CmpEQ,
+            CmpGT,
+            CmpLT,
+            And,
+            Or,
+            Not,
+            Mod,
+            Round,
+            Abs,
+            Floor,
+            Ceil,
+            Sqrt,
+            Sin,
+            Cos,
+            Tan,
+            Asin,
+            Acos,
+            Atan,
+            Ln,
+            Log10,
+            Exp,
+            Exp10
+
+        };
+
         void SetUp() override
         {
             test_function(nullptr, nullptr, nullptr, nullptr); // force dependency
@@ -27,27 +58,252 @@ class LLVMCodeBuilderTest : public testing::Test
         void createBuilder(Target *target, bool warp) { m_builder = std::make_unique<LLVMCodeBuilder>(target, "test", warp); }
         void createBuilder(bool warp) { createBuilder(nullptr, warp); }
 
-        void callConstFuncForType(ValueType type)
+        CompilerValue *callConstFuncForType(ValueType type, CompilerValue *arg)
         {
             switch (type) {
                 case ValueType::Number:
-                    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-                    break;
+                    return m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }, { arg });
 
                 case ValueType::Bool:
-                    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-                    break;
+                    return m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { arg });
 
                 case ValueType::String:
-                    m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String });
-                    break;
+                    return m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { arg });
+
+                default:
+                    EXPECT_TRUE(false);
+                    return nullptr;
+            }
+        }
+
+        CompilerValue *addOp(OpType type, CompilerValue *arg1, CompilerValue *arg2)
+        {
+            switch (type) {
+                case OpType::Add:
+                    return m_builder->createAdd(arg1, arg2);
+
+                case OpType::Sub:
+                    return m_builder->createSub(arg1, arg2);
+
+                case OpType::Mul:
+                    return m_builder->createMul(arg1, arg2);
+
+                case OpType::Div:
+                    return m_builder->createDiv(arg1, arg2);
+
+                case OpType::CmpEQ:
+                    return m_builder->createCmpEQ(arg1, arg2);
+
+                case OpType::CmpGT:
+                    return m_builder->createCmpGT(arg1, arg2);
+
+                case OpType::CmpLT:
+                    return m_builder->createCmpLT(arg1, arg2);
+
+                case OpType::And:
+                    return m_builder->createAnd(arg1, arg2);
+
+                case OpType::Or:
+                    return m_builder->createOr(arg1, arg2);
+
+                case OpType::Mod:
+                    return m_builder->createMod(arg1, arg2);
+
+                default:
+                    EXPECT_TRUE(false);
+                    return nullptr;
+            }
+        }
+
+        CompilerValue *addOp(OpType type, CompilerValue *arg)
+        {
+            switch (type) {
+                case OpType::Not:
+                    return m_builder->createNot(arg);
+
+                case OpType::Round:
+                    return m_builder->createRound(arg);
+
+                case OpType::Abs:
+                    return m_builder->createAbs(arg);
+
+                case OpType::Floor:
+                    return m_builder->createFloor(arg);
+
+                case OpType::Ceil:
+                    return m_builder->createCeil(arg);
+
+                case OpType::Sqrt:
+                    return m_builder->createSqrt(arg);
+
+                case OpType::Sin:
+                    return m_builder->createSin(arg);
+
+                case OpType::Cos:
+                    return m_builder->createCos(arg);
+
+                case OpType::Tan:
+                    return m_builder->createTan(arg);
+
+                case OpType::Asin:
+                    return m_builder->createAsin(arg);
+
+                case OpType::Acos:
+                    return m_builder->createAcos(arg);
+
+                case OpType::Atan:
+                    return m_builder->createAtan(arg);
+
+                case OpType::Ln:
+                    return m_builder->createLn(arg);
+
+                case OpType::Log10:
+                    return m_builder->createLog10(arg);
+
+                case OpType::Exp:
+                    return m_builder->createExp(arg);
+
+                case OpType::Exp10:
+                    return m_builder->createExp10(arg);
+
+                default:
+                    EXPECT_TRUE(false);
+                    return nullptr;
+            }
+        }
+
+        Value doOp(OpType type, const Value &v1, const Value &v2)
+        {
+            switch (type) {
+                case OpType::Add:
+                    return v1 + v2;
+
+                case OpType::Sub:
+                    return v1 - v2;
+
+                case OpType::Mul:
+                    return v1 * v2;
+
+                case OpType::Div:
+                    return v1 / v2;
+
+                case OpType::CmpEQ:
+                    return v1 == v2;
+
+                case OpType::CmpGT:
+                    return v1 > v2;
+
+                case OpType::CmpLT:
+                    return v1 < v2;
+
+                case OpType::And:
+                    return v1.toBool() && v2.toBool();
+
+                case OpType::Or:
+                    return v1.toBool() || v2.toBool();
+
+                case OpType::Mod:
+                    return v1 % v2;
 
                 default:
-                    FAIL();
-                    break;
+                    EXPECT_TRUE(false);
+                    return Value();
             }
         }
 
+        Value doOp(OpType type, const Value &v)
+        {
+            switch (type) {
+                case OpType::Not:
+                    return !v.toBool();
+
+                default:
+                    EXPECT_TRUE(false);
+                    return Value();
+            }
+        }
+
+        void runOpTest(OpType type, const Value &v1, const Value &v2)
+        {
+            createBuilder(true);
+
+            CompilerValue *arg1 = m_builder->addConstValue(v1);
+            CompilerValue *arg2 = m_builder->addConstValue(v2);
+            CompilerValue *ret = addOp(type, arg1, arg2);
+            m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret });
+
+            arg1 = m_builder->addConstValue(v1);
+            arg1 = callConstFuncForType(v1.type(), arg1);
+            arg2 = m_builder->addConstValue(v2);
+            arg2 = callConstFuncForType(v2.type(), arg2);
+            ret = addOp(type, arg1, arg2);
+            m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret });
+
+            std::string str = doOp(type, v1, v2).toString() + '\n';
+            std::string expected = str + str;
+
+            auto code = m_builder->finalize();
+            auto ctx = code->createExecutionContext(&m_target);
+
+            testing::internal::CaptureStdout();
+            code->run(ctx.get());
+            const std::string quotes1 = v1.isString() ? "\"" : "";
+            const std::string quotes2 = v2.isString() ? "\"" : "";
+            ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
+        };
+
+        void runOpTest(OpType type, const Value &v)
+        {
+            createBuilder(true);
+
+            CompilerValue *arg = m_builder->addConstValue(v);
+            CompilerValue *ret = addOp(type, arg);
+            m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret });
+
+            arg = m_builder->addConstValue(v);
+            arg = callConstFuncForType(v.type(), arg);
+            ret = addOp(type, arg);
+            m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret });
+
+            std::string str = doOp(type, v).toString() + '\n';
+            std::string expected = str + str;
+
+            auto code = m_builder->finalize();
+            auto ctx = code->createExecutionContext(&m_target);
+
+            testing::internal::CaptureStdout();
+            code->run(ctx.get());
+            const std::string quotes = v.isString() ? "\"" : "";
+            ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "NOT: " << quotes << v.toString() << quotes;
+        };
+
+        void runUnaryNumOpTest(OpType type, const Value &v, double expectedResult)
+        {
+            createBuilder(true);
+
+            CompilerValue *arg = m_builder->addConstValue(v);
+            CompilerValue *ret = addOp(type, arg);
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { ret });
+
+            arg = m_builder->addConstValue(v);
+            arg = callConstFuncForType(v.type(), arg);
+            ret = addOp(type, arg);
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { ret });
+
+            std::stringstream stream;
+            stream << expectedResult;
+            std::string str = stream.str() + '\n';
+            std::string expected = str + str;
+
+            auto code = m_builder->finalize();
+            auto ctx = code->createExecutionContext(&m_target);
+
+            testing::internal::CaptureStdout();
+            code->run(ctx.get());
+            const std::string quotes = v.isString() ? "\"" : "";
+            ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes << v.toString() << quotes;
+        };
+
         std::unique_ptr<LLVMCodeBuilder> m_builder;
         TargetMock m_target; // NOTE: isStage() is used for call expectations
 };
@@ -58,22 +314,26 @@ TEST_F(LLVMCodeBuilderTest, FunctionCalls)
 
     for (bool warp : warpList) {
         createBuilder(warp);
-        m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
-
-        m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {});
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue("1");
-        m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String });
-        m_builder->addConstValue("2");
-        m_builder->addConstValue("3");
-        m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String });
-
-        m_builder->addConstValue("test");
-        m_builder->addConstValue("4");
-        m_builder->addConstValue("5");
-        m_builder->addFunctionCall("test_function_3_args_ret", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String });
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+
+        CompilerValue *v = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+        v = m_builder->addConstValue("1");
+        v = m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v });
+        CompilerValue *v1 = m_builder->addConstValue("2");
+        CompilerValue *v2 = m_builder->addConstValue("3");
+        m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 });
+
+        v = m_builder->addConstValue("test");
+        v1 = m_builder->addConstValue("4");
+        v2 = m_builder->addConstValue("5");
+        v = m_builder->addFunctionCall(
+            "test_function_3_args_ret",
+            Compiler::StaticType::String,
+            { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String },
+            { v, v1, v2 });
+        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         auto code = m_builder->finalize();
         auto ctx = code->createExecutionContext(&m_target);
 
@@ -98,25 +358,25 @@ TEST_F(LLVMCodeBuilderTest, ConstCasting)
 {
     createBuilder(true);
 
-    m_builder->addConstValue(5.2);
-    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-    m_builder->addConstValue("-24.156");
-    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+    CompilerValue *v = m_builder->addConstValue(5.2);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+    v = m_builder->addConstValue("-24.156");
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
 
-    m_builder->addConstValue(true);
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue(true);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue(0);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
-    m_builder->addConstValue("false");
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue("false");
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
-    m_builder->addConstValue("123");
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("123");
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("hello world");
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("hello world");
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     auto code = m_builder->finalize();
     auto ctx = code->createExecutionContext(&m_target);
@@ -140,69 +400,69 @@ TEST_F(LLVMCodeBuilderTest, RawValueCasting)
     createBuilder(true);
 
     // Number -> number
-    m_builder->addConstValue(5.2);
-    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+    CompilerValue *v = m_builder->addConstValue(5.2);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
 
     // Number -> bool
-    m_builder->addConstValue(-24.156);
-    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue(-24.156);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue(0);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
     // Number -> string
-    m_builder->addConstValue(59.8);
-    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(59.8);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     // Bool -> number
-    m_builder->addConstValue(true);
-    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+    v = m_builder->addConstValue(true);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
 
-    m_builder->addConstValue(false);
-    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+    v = m_builder->addConstValue(false);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
 
     // Bool -> bool
-    m_builder->addConstValue(true);
-    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue(true);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
-    m_builder->addConstValue(false);
-    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue(false);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
     // Bool -> string
-    m_builder->addConstValue(true);
-    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(true);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(false);
-    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(false);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     // String -> number
-    m_builder->addConstValue("5.2");
-    m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String });
-    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
+    v = m_builder->addConstValue("5.2");
+    v = callConstFuncForType(ValueType::String, v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
 
     // String -> bool
-    m_builder->addConstValue("abc");
-    m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String });
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue("abc");
+    v = callConstFuncForType(ValueType::String, v);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
-    m_builder->addConstValue("false");
-    m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String });
-    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool });
+    v = m_builder->addConstValue("false");
+    v = callConstFuncForType(ValueType::String, v);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
     // String -> string
-    m_builder->addConstValue("hello world");
-    m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String });
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("hello world");
+    v = callConstFuncForType(ValueType::String, v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     auto code = m_builder->finalize();
     auto ctx = code->createExecutionContext(&m_target);
@@ -230,1557 +490,835 @@ TEST_F(LLVMCodeBuilderTest, RawValueCasting)
 
 TEST_F(LLVMCodeBuilderTest, Add)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, Value v2) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createAdd();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->addConstValue(v2);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createAdd();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = (v1 + v2).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(50, 25);
-    addOpTest(-500, 25);
-    addOpTest(-500, -25);
-    addOpTest("2.54", "6.28");
-    addOpTest(2.54, "-6.28");
-    addOpTest(true, true);
-    addOpTest("Infinity", "Infinity");
-    addOpTest("Infinity", "-Infinity");
-    addOpTest("-Infinity", "Infinity");
-    addOpTest("-Infinity", "-Infinity");
-    addOpTest(1, "NaN");
-    addOpTest("NaN", 1);
+    runOpTest(OpType::Add, 50, 25);
+    runOpTest(OpType::Add, -500, 25);
+    runOpTest(OpType::Add, -500, -25);
+    runOpTest(OpType::Add, "2.54", "6.28");
+    runOpTest(OpType::Add, 2.54, "-6.28");
+    runOpTest(OpType::Add, true, true);
+    runOpTest(OpType::Add, "Infinity", "Infinity");
+    runOpTest(OpType::Add, "Infinity", "-Infinity");
+    runOpTest(OpType::Add, "-Infinity", "Infinity");
+    runOpTest(OpType::Add, "-Infinity", "-Infinity");
+    runOpTest(OpType::Add, 1, "NaN");
+    runOpTest(OpType::Add, "NaN", 1);
 }
 
 TEST_F(LLVMCodeBuilderTest, Subtract)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, Value v2) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createSub();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->addConstValue(v2);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createSub();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = (v1 - v2).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(50, 25);
-    addOpTest(-500, 25);
-    addOpTest(-500, -25);
-    addOpTest("2.54", "6.28");
-    addOpTest(2.54, "-6.28");
-    addOpTest(true, true);
-    addOpTest("Infinity", "Infinity");
-    addOpTest("Infinity", "-Infinity");
-    addOpTest("-Infinity", "Infinity");
-    addOpTest("-Infinity", "-Infinity");
-    addOpTest(1, "NaN");
-    addOpTest("NaN", 1);
+    runOpTest(OpType::Sub, 50, 25);
+    runOpTest(OpType::Sub, -500, 25);
+    runOpTest(OpType::Sub, -500, -25);
+    runOpTest(OpType::Sub, "2.54", "6.28");
+    runOpTest(OpType::Sub, 2.54, "-6.28");
+    runOpTest(OpType::Sub, true, true);
+    runOpTest(OpType::Sub, "Infinity", "Infinity");
+    runOpTest(OpType::Sub, "Infinity", "-Infinity");
+    runOpTest(OpType::Sub, "-Infinity", "Infinity");
+    runOpTest(OpType::Sub, "-Infinity", "-Infinity");
+    runOpTest(OpType::Sub, 1, "NaN");
+    runOpTest(OpType::Sub, "NaN", 1);
 }
 
 TEST_F(LLVMCodeBuilderTest, Multiply)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, Value v2) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createMul();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->addConstValue(v2);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createMul();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = (v1 * v2).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(50, 2);
-    addOpTest(-500, 25);
-    addOpTest("-500", -25);
-    addOpTest("2.54", "6.28");
-    addOpTest(true, true);
-    addOpTest("Infinity", "Infinity");
-    addOpTest("Infinity", 0);
-    addOpTest("Infinity", 2);
-    addOpTest("Infinity", -2);
-    addOpTest("Infinity", "-Infinity");
-    addOpTest("-Infinity", "Infinity");
-    addOpTest("-Infinity", 0);
-    addOpTest("-Infinity", 2);
-    addOpTest("-Infinity", -2);
-    addOpTest("-Infinity", "-Infinity");
-    addOpTest(1, "NaN");
-    addOpTest("NaN", 1);
+    runOpTest(OpType::Mul, 50, 2);
+    runOpTest(OpType::Mul, -500, 25);
+    runOpTest(OpType::Mul, "-500", -25);
+    runOpTest(OpType::Mul, "2.54", "6.28");
+    runOpTest(OpType::Mul, true, true);
+    runOpTest(OpType::Mul, "Infinity", "Infinity");
+    runOpTest(OpType::Mul, "Infinity", 0);
+    runOpTest(OpType::Mul, "Infinity", 2);
+    runOpTest(OpType::Mul, "Infinity", -2);
+    runOpTest(OpType::Mul, "Infinity", "-Infinity");
+    runOpTest(OpType::Mul, "-Infinity", "Infinity");
+    runOpTest(OpType::Mul, "-Infinity", 0);
+    runOpTest(OpType::Mul, "-Infinity", 2);
+    runOpTest(OpType::Mul, "-Infinity", -2);
+    runOpTest(OpType::Mul, "-Infinity", "-Infinity");
+    runOpTest(OpType::Mul, 1, "NaN");
+    runOpTest(OpType::Mul, "NaN", 1);
 }
 
 TEST_F(LLVMCodeBuilderTest, Divide)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, Value v2) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createDiv();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->addConstValue(v2);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createDiv();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = (v1 / v2).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(50, 2);
-    addOpTest(-500, 25);
-    addOpTest("-500", -25);
-    addOpTest("3.5", "2.5");
-    addOpTest(true, true);
-    addOpTest("Infinity", "Infinity");
-    addOpTest("Infinity", 0);
-    addOpTest("Infinity", 2);
-    addOpTest("Infinity", -2);
-    addOpTest("Infinity", "-Infinity");
-    addOpTest("-Infinity", "Infinity");
-    addOpTest("-Infinity", 0);
-    addOpTest("-Infinity", 2);
-    addOpTest("-Infinity", -2);
-    addOpTest("-Infinity", "-Infinity");
-    addOpTest(0, "Infinity");
-    addOpTest(2, "Infinity");
-    addOpTest(-2, "Infinity");
-    addOpTest(0, "-Infinity");
-    addOpTest(2, "-Infinity");
-    addOpTest(-2, "-Infinity");
-    addOpTest(1, "NaN");
-    addOpTest("NaN", 1);
-    addOpTest(5, 0);
-    addOpTest(-5, 0);
-    addOpTest(0, 0);
+    runOpTest(OpType::Div, 50, 2);
+    runOpTest(OpType::Div, -500, 25);
+    runOpTest(OpType::Div, "-500", -25);
+    runOpTest(OpType::Div, "3.5", "2.5");
+    runOpTest(OpType::Div, true, true);
+    runOpTest(OpType::Div, "Infinity", "Infinity");
+    runOpTest(OpType::Div, "Infinity", 0);
+    runOpTest(OpType::Div, "Infinity", 2);
+    runOpTest(OpType::Div, "Infinity", -2);
+    runOpTest(OpType::Div, "Infinity", "-Infinity");
+    runOpTest(OpType::Div, "-Infinity", "Infinity");
+    runOpTest(OpType::Div, "-Infinity", 0);
+    runOpTest(OpType::Div, "-Infinity", 2);
+    runOpTest(OpType::Div, "-Infinity", -2);
+    runOpTest(OpType::Div, "-Infinity", "-Infinity");
+    runOpTest(OpType::Div, 0, "Infinity");
+    runOpTest(OpType::Div, 2, "Infinity");
+    runOpTest(OpType::Div, -2, "Infinity");
+    runOpTest(OpType::Div, 0, "-Infinity");
+    runOpTest(OpType::Div, 2, "-Infinity");
+    runOpTest(OpType::Div, -2, "-Infinity");
+    runOpTest(OpType::Div, 1, "NaN");
+    runOpTest(OpType::Div, "NaN", 1);
+    runOpTest(OpType::Div, 5, 0);
+    runOpTest(OpType::Div, -5, 0);
+    runOpTest(OpType::Div, 0, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, EqualComparison)
 {
-    auto addOpTest = [this](Value v1, Value v2) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createCmpEQ();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        callConstFuncForType(v1.type());
-        m_builder->addConstValue(v2);
-        callConstFuncForType(v2.type());
-        m_builder->createCmpEQ();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = Value(v1 == v2).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(10, 10);
-    addOpTest(10, 8);
-    addOpTest(8, 10);
-
-    addOpTest(-4.25, -4.25);
-    addOpTest(-4.25, 5.312);
-    addOpTest(5.312, -4.25);
-
-    addOpTest(true, true);
-    addOpTest(true, false);
-    addOpTest(false, true);
-
-    addOpTest(1, true);
-    addOpTest(1, false);
-
-    addOpTest("abC def", "abC def");
-    addOpTest("abC def", "abc dEf");
-    addOpTest("abC def", "ghi Jkl");
-    addOpTest("abC def", "hello world");
-
-    addOpTest(" ", "");
-    addOpTest(" ", "0");
-    addOpTest(" ", 0);
-    addOpTest(0, " ");
-    addOpTest("", "0");
-    addOpTest("", 0);
-    addOpTest(0, "");
-    addOpTest("0", 0);
-    addOpTest(0, "0");
-
-    addOpTest(5.25, "5.25");
-    addOpTest("5.25", 5.25);
-    addOpTest(5.25, " 5.25");
-    addOpTest(" 5.25", 5.25);
-    addOpTest(5.25, "5.25 ");
-    addOpTest("5.25 ", 5.25);
-    addOpTest(5.25, " 5.25 ");
-    addOpTest(" 5.25 ", 5.25);
-    addOpTest(5.25, "5.26");
-    addOpTest("5.26", 5.25);
-    addOpTest("5.25", "5.26");
-    addOpTest(5, "5  ");
-    addOpTest("5  ", 5);
-    addOpTest(0, "1");
-    addOpTest("1", 0);
-    addOpTest(0, "test");
-    addOpTest("test", 0);
+    runOpTest(OpType::CmpEQ, 10, 10);
+    runOpTest(OpType::CmpEQ, 10, 8);
+    runOpTest(OpType::CmpEQ, 8, 10);
+
+    runOpTest(OpType::CmpEQ, -4.25, -4.25);
+    runOpTest(OpType::CmpEQ, -4.25, 5.312);
+    runOpTest(OpType::CmpEQ, 5.312, -4.25);
+
+    runOpTest(OpType::CmpEQ, true, true);
+    runOpTest(OpType::CmpEQ, true, false);
+    runOpTest(OpType::CmpEQ, false, true);
+
+    runOpTest(OpType::CmpEQ, 1, true);
+    runOpTest(OpType::CmpEQ, 1, false);
+
+    runOpTest(OpType::CmpEQ, "abC def", "abC def");
+    runOpTest(OpType::CmpEQ, "abC def", "abc dEf");
+    runOpTest(OpType::CmpEQ, "abC def", "ghi Jkl");
+    runOpTest(OpType::CmpEQ, "abC def", "hello world");
+
+    runOpTest(OpType::CmpEQ, " ", "");
+    runOpTest(OpType::CmpEQ, " ", "0");
+    runOpTest(OpType::CmpEQ, " ", 0);
+    runOpTest(OpType::CmpEQ, 0, " ");
+    runOpTest(OpType::CmpEQ, "", "0");
+    runOpTest(OpType::CmpEQ, "", 0);
+    runOpTest(OpType::CmpEQ, 0, "");
+    runOpTest(OpType::CmpEQ, "0", 0);
+    runOpTest(OpType::CmpEQ, 0, "0");
+
+    runOpTest(OpType::CmpEQ, 5.25, "5.25");
+    runOpTest(OpType::CmpEQ, "5.25", 5.25);
+    runOpTest(OpType::CmpEQ, 5.25, " 5.25");
+    runOpTest(OpType::CmpEQ, " 5.25", 5.25);
+    runOpTest(OpType::CmpEQ, 5.25, "5.25 ");
+    runOpTest(OpType::CmpEQ, "5.25 ", 5.25);
+    runOpTest(OpType::CmpEQ, 5.25, " 5.25 ");
+    runOpTest(OpType::CmpEQ, " 5.25 ", 5.25);
+    runOpTest(OpType::CmpEQ, 5.25, "5.26");
+    runOpTest(OpType::CmpEQ, "5.26", 5.25);
+    runOpTest(OpType::CmpEQ, "5.25", "5.26");
+    runOpTest(OpType::CmpEQ, 5, "5  ");
+    runOpTest(OpType::CmpEQ, "5  ", 5);
+    runOpTest(OpType::CmpEQ, 0, "1");
+    runOpTest(OpType::CmpEQ, "1", 0);
+    runOpTest(OpType::CmpEQ, 0, "test");
+    runOpTest(OpType::CmpEQ, "test", 0);
 
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(inf, inf);
-    addOpTest(-inf, -inf);
-    addOpTest(nan, nan);
-    addOpTest(inf, -inf);
-    addOpTest(-inf, inf);
-    addOpTest(inf, nan);
-    addOpTest(nan, inf);
-    addOpTest(-inf, nan);
-    addOpTest(nan, -inf);
-
-    addOpTest(5, inf);
-    addOpTest(5, -inf);
-    addOpTest(5, nan);
-    addOpTest(0, nan);
-
-    addOpTest(true, "true");
-    addOpTest("true", true);
-    addOpTest(false, "false");
-    addOpTest("false", false);
-    addOpTest(false, "true");
-    addOpTest("true", false);
-    addOpTest(true, "false");
-    addOpTest("false", true);
-    addOpTest(true, "TRUE");
-    addOpTest("TRUE", true);
-    addOpTest(false, "FALSE");
-    addOpTest("FALSE", false);
-
-    addOpTest(true, "00001");
-    addOpTest("00001", true);
-    addOpTest(true, "00000");
-    addOpTest("00000", true);
-    addOpTest(false, "00000");
-    addOpTest("00000", false);
-
-    addOpTest("true", 1);
-    addOpTest(1, "true");
-    addOpTest("true", 0);
-    addOpTest(0, "true");
-    addOpTest("false", 0);
-    addOpTest(0, "false");
-    addOpTest("false", 1);
-    addOpTest(1, "false");
-
-    addOpTest("true", "TRUE");
-    addOpTest("true", "FALSE");
-    addOpTest("false", "FALSE");
-    addOpTest("false", "TRUE");
-
-    addOpTest(true, inf);
-    addOpTest(true, -inf);
-    addOpTest(true, nan);
-    addOpTest(false, inf);
-    addOpTest(false, -inf);
-    addOpTest(false, nan);
-
-    addOpTest("Infinity", inf);
-    addOpTest("Infinity", -inf);
-    addOpTest("Infinity", nan);
-    addOpTest("infinity", inf);
-    addOpTest("infinity", -inf);
-    addOpTest("infinity", nan);
-    addOpTest("-Infinity", inf);
-    addOpTest("-Infinity", -inf);
-    addOpTest("-Infinity", nan);
-    addOpTest("-infinity", inf);
-    addOpTest("-infinity", -inf);
-    addOpTest("-infinity", nan);
-    addOpTest("NaN", inf);
-    addOpTest("NaN", -inf);
-    addOpTest("NaN", nan);
-    addOpTest("nan", inf);
-    addOpTest("nan", -inf);
-    addOpTest("nan", nan);
-
-    addOpTest(inf, "abc");
-    addOpTest(inf, " ");
-    addOpTest(inf, "");
-    addOpTest(inf, "0");
-    addOpTest(-inf, "abc");
-    addOpTest(-inf, " ");
-    addOpTest(-inf, "");
-    addOpTest(-inf, "0");
-    addOpTest(nan, "abc");
-    addOpTest(nan, " ");
-    addOpTest(nan, "");
-    addOpTest(nan, "0");
+    runOpTest(OpType::CmpEQ, inf, inf);
+    runOpTest(OpType::CmpEQ, -inf, -inf);
+    runOpTest(OpType::CmpEQ, nan, nan);
+    runOpTest(OpType::CmpEQ, inf, -inf);
+    runOpTest(OpType::CmpEQ, -inf, inf);
+    runOpTest(OpType::CmpEQ, inf, nan);
+    runOpTest(OpType::CmpEQ, nan, inf);
+    runOpTest(OpType::CmpEQ, -inf, nan);
+    runOpTest(OpType::CmpEQ, nan, -inf);
+
+    runOpTest(OpType::CmpEQ, 5, inf);
+    runOpTest(OpType::CmpEQ, 5, -inf);
+    runOpTest(OpType::CmpEQ, 5, nan);
+    runOpTest(OpType::CmpEQ, 0, nan);
+
+    runOpTest(OpType::CmpEQ, true, "true");
+    runOpTest(OpType::CmpEQ, "true", true);
+    runOpTest(OpType::CmpEQ, false, "false");
+    runOpTest(OpType::CmpEQ, "false", false);
+    runOpTest(OpType::CmpEQ, false, "true");
+    runOpTest(OpType::CmpEQ, "true", false);
+    runOpTest(OpType::CmpEQ, true, "false");
+    runOpTest(OpType::CmpEQ, "false", true);
+    runOpTest(OpType::CmpEQ, true, "TRUE");
+    runOpTest(OpType::CmpEQ, "TRUE", true);
+    runOpTest(OpType::CmpEQ, false, "FALSE");
+    runOpTest(OpType::CmpEQ, "FALSE", false);
+
+    runOpTest(OpType::CmpEQ, true, "00001");
+    runOpTest(OpType::CmpEQ, "00001", true);
+    runOpTest(OpType::CmpEQ, true, "00000");
+    runOpTest(OpType::CmpEQ, "00000", true);
+    runOpTest(OpType::CmpEQ, false, "00000");
+    runOpTest(OpType::CmpEQ, "00000", false);
+
+    runOpTest(OpType::CmpEQ, "true", 1);
+    runOpTest(OpType::CmpEQ, 1, "true");
+    runOpTest(OpType::CmpEQ, "true", 0);
+    runOpTest(OpType::CmpEQ, 0, "true");
+    runOpTest(OpType::CmpEQ, "false", 0);
+    runOpTest(OpType::CmpEQ, 0, "false");
+    runOpTest(OpType::CmpEQ, "false", 1);
+    runOpTest(OpType::CmpEQ, 1, "false");
+
+    runOpTest(OpType::CmpEQ, "true", "TRUE");
+    runOpTest(OpType::CmpEQ, "true", "FALSE");
+    runOpTest(OpType::CmpEQ, "false", "FALSE");
+    runOpTest(OpType::CmpEQ, "false", "TRUE");
+
+    runOpTest(OpType::CmpEQ, true, inf);
+    runOpTest(OpType::CmpEQ, true, -inf);
+    runOpTest(OpType::CmpEQ, true, nan);
+    runOpTest(OpType::CmpEQ, false, inf);
+    runOpTest(OpType::CmpEQ, false, -inf);
+    runOpTest(OpType::CmpEQ, false, nan);
+
+    runOpTest(OpType::CmpEQ, "Infinity", inf);
+    runOpTest(OpType::CmpEQ, "Infinity", -inf);
+    runOpTest(OpType::CmpEQ, "Infinity", nan);
+    runOpTest(OpType::CmpEQ, "infinity", inf);
+    runOpTest(OpType::CmpEQ, "infinity", -inf);
+    runOpTest(OpType::CmpEQ, "infinity", nan);
+    runOpTest(OpType::CmpEQ, "-Infinity", inf);
+    runOpTest(OpType::CmpEQ, "-Infinity", -inf);
+    runOpTest(OpType::CmpEQ, "-Infinity", nan);
+    runOpTest(OpType::CmpEQ, "-infinity", inf);
+    runOpTest(OpType::CmpEQ, "-infinity", -inf);
+    runOpTest(OpType::CmpEQ, "-infinity", nan);
+    runOpTest(OpType::CmpEQ, "NaN", inf);
+    runOpTest(OpType::CmpEQ, "NaN", -inf);
+    runOpTest(OpType::CmpEQ, "NaN", nan);
+    runOpTest(OpType::CmpEQ, "nan", inf);
+    runOpTest(OpType::CmpEQ, "nan", -inf);
+    runOpTest(OpType::CmpEQ, "nan", nan);
+
+    runOpTest(OpType::CmpEQ, inf, "abc");
+    runOpTest(OpType::CmpEQ, inf, " ");
+    runOpTest(OpType::CmpEQ, inf, "");
+    runOpTest(OpType::CmpEQ, inf, "0");
+    runOpTest(OpType::CmpEQ, -inf, "abc");
+    runOpTest(OpType::CmpEQ, -inf, " ");
+    runOpTest(OpType::CmpEQ, -inf, "");
+    runOpTest(OpType::CmpEQ, -inf, "0");
+    runOpTest(OpType::CmpEQ, nan, "abc");
+    runOpTest(OpType::CmpEQ, nan, " ");
+    runOpTest(OpType::CmpEQ, nan, "");
+    runOpTest(OpType::CmpEQ, nan, "0");
 }
 
 TEST_F(LLVMCodeBuilderTest, GreaterAndLowerThanComparison)
 {
-    auto addOpTest = [this](Value v1, Value v2) {
-        // GT
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createCmpGT();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        callConstFuncForType(v1.type());
-        m_builder->addConstValue(v2);
-        callConstFuncForType(v2.type());
-        m_builder->createCmpGT();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = Value(v1 > v2).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "GT: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-
-        // LT
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createCmpLT();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        callConstFuncForType(v1.type());
-        m_builder->addConstValue(v2);
-        callConstFuncForType(v2.type());
-        m_builder->createCmpLT();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        str = Value(v1 < v2).toString() + '\n';
-        expected = str + str;
-
-        code = m_builder->finalize();
-        ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "LT: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(10, 10);
-    addOpTest(10, 8);
-    addOpTest(8, 10);
-
-    addOpTest(-4.25, -4.25);
-    addOpTest(-4.25, 5.312);
-    addOpTest(5.312, -4.25);
-
-    addOpTest(true, true);
-    addOpTest(true, false);
-    addOpTest(false, true);
-
-    addOpTest(1, true);
-    addOpTest(1, false);
-
-    addOpTest("abC def", "abC def");
-    addOpTest("abC def", "abc dEf");
-    addOpTest("abC def", "ghi Jkl");
-    addOpTest("ghi Jkl", "abC def");
-    addOpTest("abC def", "hello world");
-
-    addOpTest(" ", "");
-    addOpTest(" ", "0");
-    addOpTest(" ", 0);
-    addOpTest(0, " ");
-    addOpTest("", "0");
-    addOpTest("", 0);
-    addOpTest(0, "");
-    addOpTest("0", 0);
-    addOpTest(0, "0");
-
-    addOpTest(5.25, "5.25");
-    addOpTest("5.25", 5.25);
-    addOpTest(5.25, " 5.25");
-    addOpTest(" 5.25", 5.25);
-    addOpTest(5.25, "5.25 ");
-    addOpTest("5.25 ", 5.25);
-    addOpTest(5.25, " 5.25 ");
-    addOpTest(" 5.25 ", 5.25);
-    addOpTest(5.25, "5.26");
-    addOpTest("5.26", 5.25);
-    addOpTest("5.25", "5.26");
-    addOpTest(5, "5  ");
-    addOpTest("5  ", 5);
-    addOpTest(0, "1");
-    addOpTest("1", 0);
-    addOpTest(0, "test");
-    addOpTest("test", 0);
-
-    static const double inf = std::numeric_limits<double>::infinity();
-    static const double nan = std::numeric_limits<double>::quiet_NaN();
-
-    addOpTest(inf, inf);
-    addOpTest(-inf, -inf);
-    addOpTest(nan, nan);
-    addOpTest(inf, -inf);
-    addOpTest(-inf, inf);
-    addOpTest(inf, nan);
-    addOpTest(nan, inf);
-    addOpTest(-inf, nan);
-    addOpTest(nan, -inf);
-
-    addOpTest(5, inf);
-    addOpTest(inf, 5);
-    addOpTest(5, -inf);
-    addOpTest(-inf, 5);
-    addOpTest(5, nan);
-    addOpTest(nan, 5);
-    addOpTest(0, nan);
-    addOpTest(nan, 0);
-
-    addOpTest(true, "true");
-    addOpTest("true", true);
-    addOpTest(false, "false");
-    addOpTest("false", false);
-    addOpTest(false, "true");
-    addOpTest("true", false);
-    addOpTest(true, "false");
-    addOpTest("false", true);
-    addOpTest(true, "TRUE");
-    addOpTest("TRUE", true);
-    addOpTest(false, "FALSE");
-    addOpTest("FALSE", false);
-
-    addOpTest(true, "00001");
-    addOpTest("00001", true);
-    addOpTest(true, "00000");
-    addOpTest("00000", true);
-    addOpTest(false, "00000");
-    addOpTest("00000", false);
-
-    addOpTest("true", 1);
-    addOpTest(1, "true");
-    addOpTest("true", 0);
-    addOpTest(0, "true");
-    addOpTest("false", 0);
-    addOpTest(0, "false");
-    addOpTest("false", 1);
-    addOpTest(1, "false");
-
-    addOpTest("true", "TRUE");
-    addOpTest("true", "FALSE");
-    addOpTest("false", "FALSE");
-    addOpTest("false", "TRUE");
-
-    addOpTest(true, inf);
-    addOpTest(inf, true);
-    addOpTest(true, -inf);
-    addOpTest(-inf, true);
-    addOpTest(true, nan);
-    addOpTest(nan, true);
-    addOpTest(false, inf);
-    addOpTest(inf, false);
-    addOpTest(false, -inf);
-    addOpTest(-inf, false);
-    addOpTest(false, nan);
-    addOpTest(nan, false);
-
-    addOpTest("Infinity", inf);
-    addOpTest("Infinity", -inf);
-    addOpTest("Infinity", nan);
-    addOpTest("infinity", inf);
-    addOpTest("infinity", -inf);
-    addOpTest("infinity", nan);
-    addOpTest("-Infinity", inf);
-    addOpTest("-Infinity", -inf);
-    addOpTest("-Infinity", nan);
-    addOpTest("-infinity", inf);
-    addOpTest("-infinity", -inf);
-    addOpTest("-infinity", nan);
-    addOpTest("NaN", inf);
-    addOpTest("NaN", -inf);
-    addOpTest("NaN", nan);
-    addOpTest("nan", inf);
-    addOpTest("nan", -inf);
-    addOpTest("nan", nan);
-
-    addOpTest(inf, "abc");
-    addOpTest(inf, " ");
-    addOpTest(inf, "");
-    addOpTest(inf, "0");
-    addOpTest(-inf, "abc");
-    addOpTest(-inf, " ");
-    addOpTest(-inf, "");
-    addOpTest(-inf, "0");
-    addOpTest(nan, "abc");
-    addOpTest(nan, " ");
-    addOpTest(nan, "");
-    addOpTest(nan, "0");
+    std::vector<OpType> opTypes = { OpType::CmpGT, OpType::CmpLT };
+
+    for (OpType type : opTypes) {
+        runOpTest(type, 10, 10);
+        runOpTest(type, 10, 8);
+        runOpTest(type, 8, 10);
+
+        runOpTest(type, -4.25, -4.25);
+        runOpTest(type, -4.25, 5.312);
+        runOpTest(type, 5.312, -4.25);
+
+        runOpTest(type, true, true);
+        runOpTest(type, true, false);
+        runOpTest(type, false, true);
+
+        runOpTest(type, 1, true);
+        runOpTest(type, 1, false);
+
+        runOpTest(type, "abC def", "abC def");
+        runOpTest(type, "abC def", "abc dEf");
+        runOpTest(type, "abC def", "ghi Jkl");
+        runOpTest(type, "ghi Jkl", "abC def");
+        runOpTest(type, "abC def", "hello world");
+
+        runOpTest(type, " ", "");
+        runOpTest(type, " ", "0");
+        runOpTest(type, " ", 0);
+        runOpTest(type, 0, " ");
+        runOpTest(type, "", "0");
+        runOpTest(type, "", 0);
+        runOpTest(type, 0, "");
+        runOpTest(type, "0", 0);
+        runOpTest(type, 0, "0");
+
+        runOpTest(type, 5.25, "5.25");
+        runOpTest(type, "5.25", 5.25);
+        runOpTest(type, 5.25, " 5.25");
+        runOpTest(type, " 5.25", 5.25);
+        runOpTest(type, 5.25, "5.25 ");
+        runOpTest(type, "5.25 ", 5.25);
+        runOpTest(type, 5.25, " 5.25 ");
+        runOpTest(type, " 5.25 ", 5.25);
+        runOpTest(type, 5.25, "5.26");
+        runOpTest(type, "5.26", 5.25);
+        runOpTest(type, "5.25", "5.26");
+        runOpTest(type, 5, "5  ");
+        runOpTest(type, "5  ", 5);
+        runOpTest(type, 0, "1");
+        runOpTest(type, "1", 0);
+        runOpTest(type, 0, "test");
+        runOpTest(type, "test", 0);
+
+        static const double inf = std::numeric_limits<double>::infinity();
+        static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+        runOpTest(type, inf, inf);
+        runOpTest(type, -inf, -inf);
+        runOpTest(type, nan, nan);
+        runOpTest(type, inf, -inf);
+        runOpTest(type, -inf, inf);
+        runOpTest(type, inf, nan);
+        runOpTest(type, nan, inf);
+        runOpTest(type, -inf, nan);
+        runOpTest(type, nan, -inf);
+
+        runOpTest(type, 5, inf);
+        runOpTest(type, inf, 5);
+        runOpTest(type, 5, -inf);
+        runOpTest(type, -inf, 5);
+        runOpTest(type, 5, nan);
+        runOpTest(type, nan, 5);
+        runOpTest(type, 0, nan);
+        runOpTest(type, nan, 0);
+
+        runOpTest(type, true, "true");
+        runOpTest(type, "true", true);
+        runOpTest(type, false, "false");
+        runOpTest(type, "false", false);
+        runOpTest(type, false, "true");
+        runOpTest(type, "true", false);
+        runOpTest(type, true, "false");
+        runOpTest(type, "false", true);
+        runOpTest(type, true, "TRUE");
+        runOpTest(type, "TRUE", true);
+        runOpTest(type, false, "FALSE");
+        runOpTest(type, "FALSE", false);
+
+        runOpTest(type, true, "00001");
+        runOpTest(type, "00001", true);
+        runOpTest(type, true, "00000");
+        runOpTest(type, "00000", true);
+        runOpTest(type, false, "00000");
+        runOpTest(type, "00000", false);
+
+        runOpTest(type, "true", 1);
+        runOpTest(type, 1, "true");
+        runOpTest(type, "true", 0);
+        runOpTest(type, 0, "true");
+        runOpTest(type, "false", 0);
+        runOpTest(type, 0, "false");
+        runOpTest(type, "false", 1);
+        runOpTest(type, 1, "false");
+
+        runOpTest(type, "true", "TRUE");
+        runOpTest(type, "true", "FALSE");
+        runOpTest(type, "false", "FALSE");
+        runOpTest(type, "false", "TRUE");
+
+        runOpTest(type, true, inf);
+        runOpTest(type, inf, true);
+        runOpTest(type, true, -inf);
+        runOpTest(type, -inf, true);
+        runOpTest(type, true, nan);
+        runOpTest(type, nan, true);
+        runOpTest(type, false, inf);
+        runOpTest(type, inf, false);
+        runOpTest(type, false, -inf);
+        runOpTest(type, -inf, false);
+        runOpTest(type, false, nan);
+        runOpTest(type, nan, false);
+
+        runOpTest(type, "Infinity", inf);
+        runOpTest(type, "Infinity", -inf);
+        runOpTest(type, "Infinity", nan);
+        runOpTest(type, "infinity", inf);
+        runOpTest(type, "infinity", -inf);
+        runOpTest(type, "infinity", nan);
+        runOpTest(type, "-Infinity", inf);
+        runOpTest(type, "-Infinity", -inf);
+        runOpTest(type, "-Infinity", nan);
+        runOpTest(type, "-infinity", inf);
+        runOpTest(type, "-infinity", -inf);
+        runOpTest(type, "-infinity", nan);
+        runOpTest(type, "NaN", inf);
+        runOpTest(type, "NaN", -inf);
+        runOpTest(type, "NaN", nan);
+        runOpTest(type, "nan", inf);
+        runOpTest(type, "nan", -inf);
+        runOpTest(type, "nan", nan);
+
+        runOpTest(type, inf, "abc");
+        runOpTest(type, inf, " ");
+        runOpTest(type, inf, "");
+        runOpTest(type, inf, "0");
+        runOpTest(type, -inf, "abc");
+        runOpTest(type, -inf, " ");
+        runOpTest(type, -inf, "");
+        runOpTest(type, -inf, "0");
+        runOpTest(type, nan, "abc");
+        runOpTest(type, nan, " ");
+        runOpTest(type, nan, "");
+        runOpTest(type, nan, "0");
+    }
 }
 
 TEST_F(LLVMCodeBuilderTest, AndOr)
 {
-    auto addOpTest = [this](Value v1, Value v2) {
-        // And
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createAnd();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        callConstFuncForType(v1.type());
-        m_builder->addConstValue(v2);
-        callConstFuncForType(v2.type());
-        m_builder->createAnd();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = Value(v1.toBool() && v2.toBool()).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "AND: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-
-        // Or
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createOr();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        callConstFuncForType(v1.type());
-        m_builder->addConstValue(v2);
-        callConstFuncForType(v2.type());
-        m_builder->createOr();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        str = Value(v1.toBool() || v2.toBool()).toString() + '\n';
-        expected = str + str;
-
-        code = m_builder->finalize();
-        ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "OR: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(10, 8);
-    addOpTest(-4.25, -4.25);
-    addOpTest(-4.25, 5.312);
-
-    addOpTest(true, true);
-    addOpTest(true, false);
-    addOpTest(false, true);
-
-    addOpTest(1, true);
-    addOpTest(1, false);
-
-    addOpTest("abc", "def");
-    addOpTest("true", "true");
-    addOpTest("true", "false");
-    addOpTest("false", "true");
-    addOpTest("false", "false");
-
-    addOpTest(5.25, "5.25");
-    addOpTest("5.25", 5.25);
-    addOpTest(0, "1");
-    addOpTest("1", 0);
-    addOpTest(0, "test");
-    addOpTest("test", 0);
-
-    static const double inf = std::numeric_limits<double>::infinity();
-    static const double nan = std::numeric_limits<double>::quiet_NaN();
-
-    addOpTest(inf, inf);
-    addOpTest(-inf, -inf);
-    addOpTest(nan, nan);
-    addOpTest(inf, -inf);
-    addOpTest(-inf, inf);
-    addOpTest(inf, nan);
-    addOpTest(nan, inf);
-    addOpTest(-inf, nan);
-    addOpTest(nan, -inf);
-
-    addOpTest(true, "true");
-    addOpTest("true", true);
-    addOpTest(false, "false");
-    addOpTest("false", false);
-    addOpTest(false, "true");
-    addOpTest("true", false);
-    addOpTest(true, "false");
-    addOpTest("false", true);
-    addOpTest(true, "TRUE");
-    addOpTest("TRUE", true);
-    addOpTest(false, "FALSE");
-    addOpTest("FALSE", false);
-
-    addOpTest(true, "00001");
-    addOpTest("00001", true);
-    addOpTest(true, "00000");
-    addOpTest("00000", true);
-    addOpTest(false, "00000");
-    addOpTest("00000", false);
-
-    addOpTest("true", 1);
-    addOpTest(1, "true");
-    addOpTest("true", 0);
-    addOpTest(0, "true");
-    addOpTest("false", 0);
-    addOpTest(0, "false");
-    addOpTest("false", 1);
-    addOpTest(1, "false");
-
-    addOpTest("true", "TRUE");
-    addOpTest("true", "FALSE");
-    addOpTest("false", "FALSE");
-    addOpTest("false", "TRUE");
-
-    addOpTest(true, inf);
-    addOpTest(inf, true);
-    addOpTest(true, -inf);
-    addOpTest(-inf, true);
-    addOpTest(true, nan);
-    addOpTest(nan, true);
-    addOpTest(false, inf);
-    addOpTest(inf, false);
-    addOpTest(false, -inf);
-    addOpTest(-inf, false);
-    addOpTest(false, nan);
-    addOpTest(nan, false);
-
-    addOpTest("Infinity", inf);
-    addOpTest("Infinity", -inf);
-    addOpTest("Infinity", nan);
-    addOpTest("infinity", inf);
-    addOpTest("infinity", -inf);
-    addOpTest("infinity", nan);
-    addOpTest("-Infinity", inf);
-    addOpTest("-Infinity", -inf);
-    addOpTest("-Infinity", nan);
-    addOpTest("-infinity", inf);
-    addOpTest("-infinity", -inf);
-    addOpTest("-infinity", nan);
-    addOpTest("NaN", inf);
-    addOpTest("NaN", -inf);
-    addOpTest("NaN", nan);
-    addOpTest("nan", inf);
-    addOpTest("nan", -inf);
-    addOpTest("nan", nan);
+    std::vector<OpType> opTypes = { OpType::And, OpType::Or };
+
+    for (OpType type : opTypes) {
+        runOpTest(type, 10, 8);
+        runOpTest(type, -4.25, -4.25);
+        runOpTest(type, -4.25, 5.312);
+
+        runOpTest(type, true, true);
+        runOpTest(type, true, false);
+        runOpTest(type, false, true);
+
+        runOpTest(type, 1, true);
+        runOpTest(type, 1, false);
+
+        runOpTest(type, "abc", "def");
+        runOpTest(type, "true", "true");
+        runOpTest(type, "true", "false");
+        runOpTest(type, "false", "true");
+        runOpTest(type, "false", "false");
+
+        runOpTest(type, 5.25, "5.25");
+        runOpTest(type, "5.25", 5.25);
+        runOpTest(type, 0, "1");
+        runOpTest(type, "1", 0);
+        runOpTest(type, 0, "test");
+        runOpTest(type, "test", 0);
+
+        static const double inf = std::numeric_limits<double>::infinity();
+        static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+        runOpTest(type, inf, inf);
+        runOpTest(type, -inf, -inf);
+        runOpTest(type, nan, nan);
+        runOpTest(type, inf, -inf);
+        runOpTest(type, -inf, inf);
+        runOpTest(type, inf, nan);
+        runOpTest(type, nan, inf);
+        runOpTest(type, -inf, nan);
+        runOpTest(type, nan, -inf);
+
+        runOpTest(type, true, "true");
+        runOpTest(type, "true", true);
+        runOpTest(type, false, "false");
+        runOpTest(type, "false", false);
+        runOpTest(type, false, "true");
+        runOpTest(type, "true", false);
+        runOpTest(type, true, "false");
+        runOpTest(type, "false", true);
+        runOpTest(type, true, "TRUE");
+        runOpTest(type, "TRUE", true);
+        runOpTest(type, false, "FALSE");
+        runOpTest(type, "FALSE", false);
+
+        runOpTest(type, true, "00001");
+        runOpTest(type, "00001", true);
+        runOpTest(type, true, "00000");
+        runOpTest(type, "00000", true);
+        runOpTest(type, false, "00000");
+        runOpTest(type, "00000", false);
+
+        runOpTest(type, "true", 1);
+        runOpTest(type, 1, "true");
+        runOpTest(type, "true", 0);
+        runOpTest(type, 0, "true");
+        runOpTest(type, "false", 0);
+        runOpTest(type, 0, "false");
+        runOpTest(type, "false", 1);
+        runOpTest(type, 1, "false");
+
+        runOpTest(type, "true", "TRUE");
+        runOpTest(type, "true", "FALSE");
+        runOpTest(type, "false", "FALSE");
+        runOpTest(type, "false", "TRUE");
+
+        runOpTest(type, true, inf);
+        runOpTest(type, inf, true);
+        runOpTest(type, true, -inf);
+        runOpTest(type, -inf, true);
+        runOpTest(type, true, nan);
+        runOpTest(type, nan, true);
+        runOpTest(type, false, inf);
+        runOpTest(type, inf, false);
+        runOpTest(type, false, -inf);
+        runOpTest(type, -inf, false);
+        runOpTest(type, false, nan);
+        runOpTest(type, nan, false);
+
+        runOpTest(type, "Infinity", inf);
+        runOpTest(type, "Infinity", -inf);
+        runOpTest(type, "Infinity", nan);
+        runOpTest(type, "infinity", inf);
+        runOpTest(type, "infinity", -inf);
+        runOpTest(type, "infinity", nan);
+        runOpTest(type, "-Infinity", inf);
+        runOpTest(type, "-Infinity", -inf);
+        runOpTest(type, "-Infinity", nan);
+        runOpTest(type, "-infinity", inf);
+        runOpTest(type, "-infinity", -inf);
+        runOpTest(type, "-infinity", nan);
+        runOpTest(type, "NaN", inf);
+        runOpTest(type, "NaN", -inf);
+        runOpTest(type, "NaN", nan);
+        runOpTest(type, "nan", inf);
+        runOpTest(type, "nan", -inf);
+        runOpTest(type, "nan", nan);
+    }
 }
 
 TEST_F(LLVMCodeBuilderTest, Not)
 {
-    auto addOpTest = [this](Value v) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v);
-        m_builder->createNot();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v);
-        callConstFuncForType(v.type());
-        m_builder->createNot();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    runOpTest(OpType::Not, 10);
+    runOpTest(OpType::Not, -4.25);
+    runOpTest(OpType::Not, 5.312);
+    runOpTest(OpType::Not, 1);
+    runOpTest(OpType::Not, 0);
 
-        std::string str = Value(!v.toBool()).toString() + '\n';
-        std::string expected = str + str;
+    runOpTest(OpType::Not, true);
+    runOpTest(OpType::Not, false);
 
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes = v.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "NOT: " << quotes << v.toString() << quotes;
-    };
-
-    addOpTest(10);
-    addOpTest(-4.25);
-    addOpTest(5.312);
-    addOpTest(1);
-    addOpTest(0);
-
-    addOpTest(true);
-    addOpTest(false);
-
-    addOpTest("abc");
-    addOpTest("5.25");
-    addOpTest("0");
+    runOpTest(OpType::Not, "abc");
+    runOpTest(OpType::Not, "5.25");
+    runOpTest(OpType::Not, "0");
 
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(inf);
-    addOpTest(-inf);
-    addOpTest(nan);
+    runOpTest(OpType::Not, inf);
+    runOpTest(OpType::Not, -inf);
+    runOpTest(OpType::Not, nan);
 
-    addOpTest("true");
-    addOpTest("false");
-    addOpTest("TRUE");
-    addOpTest("FALSE");
+    runOpTest(OpType::Not, "true");
+    runOpTest(OpType::Not, "false");
+    runOpTest(OpType::Not, "TRUE");
+    runOpTest(OpType::Not, "FALSE");
 
-    addOpTest("00001");
-    addOpTest("00000");
+    runOpTest(OpType::Not, "00001");
+    runOpTest(OpType::Not, "00000");
 
-    addOpTest("Infinity");
-    addOpTest("infinity");
-    addOpTest("-Infinity");
-    addOpTest("-infinity");
-    addOpTest("NaN");
-    addOpTest("nan");
+    runOpTest(OpType::Not, "Infinity");
+    runOpTest(OpType::Not, "infinity");
+    runOpTest(OpType::Not, "-Infinity");
+    runOpTest(OpType::Not, "-infinity");
+    runOpTest(OpType::Not, "NaN");
+    runOpTest(OpType::Not, "nan");
 }
 
 TEST_F(LLVMCodeBuilderTest, Mod)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, Value v2) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->addConstValue(v2);
-        m_builder->createMod();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->addConstValue(v2);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createMod();
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
-
-        std::string str = (v1 % v2).toString() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        const std::string quotes2 = v2.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
-    };
-
-    addOpTest(4, 3);
-    addOpTest(3, 3);
-    addOpTest(2, 3);
-    addOpTest(1, 3);
-    addOpTest(0, 3);
-    addOpTest(-1, 3);
-    addOpTest(-2, 3);
-    addOpTest(-3, 3);
-    addOpTest(-4, 3);
-    addOpTest(4.75, 2);
-    addOpTest(-4.75, 2);
-    addOpTest(-4.75, -2);
-    addOpTest(4.75, -2);
-    addOpTest(5, 0);
-    addOpTest(-5, 0);
-    addOpTest(-2.5, "Infinity");
-    addOpTest(-1.2, "-Infinity");
-    addOpTest(2.5, "Infinity");
-    addOpTest(1.2, "-Infinity");
-    addOpTest("Infinity", 2);
-    addOpTest("-Infinity", 2);
-    addOpTest("Infinity", -2);
-    addOpTest("-Infinity", -2);
-    addOpTest(3, "NaN");
-    addOpTest(-3, "NaN");
-    addOpTest("NaN", 5);
-    addOpTest("NaN", -5);
+    runOpTest(OpType::Mod, 4, 3);
+    runOpTest(OpType::Mod, 3, 3);
+    runOpTest(OpType::Mod, 2, 3);
+    runOpTest(OpType::Mod, 1, 3);
+    runOpTest(OpType::Mod, 0, 3);
+    runOpTest(OpType::Mod, -1, 3);
+    runOpTest(OpType::Mod, -2, 3);
+    runOpTest(OpType::Mod, -3, 3);
+    runOpTest(OpType::Mod, -4, 3);
+    runOpTest(OpType::Mod, 4.75, 2);
+    runOpTest(OpType::Mod, -4.75, 2);
+    runOpTest(OpType::Mod, -4.75, -2);
+    runOpTest(OpType::Mod, 4.75, -2);
+    runOpTest(OpType::Mod, 5, 0);
+    runOpTest(OpType::Mod, -5, 0);
+    runOpTest(OpType::Mod, -2.5, "Infinity");
+    runOpTest(OpType::Mod, -1.2, "-Infinity");
+    runOpTest(OpType::Mod, 2.5, "Infinity");
+    runOpTest(OpType::Mod, 1.2, "-Infinity");
+    runOpTest(OpType::Mod, "Infinity", 2);
+    runOpTest(OpType::Mod, "-Infinity", 2);
+    runOpTest(OpType::Mod, "Infinity", -2);
+    runOpTest(OpType::Mod, "-Infinity", -2);
+    runOpTest(OpType::Mod, 3, "NaN");
+    runOpTest(OpType::Mod, -3, "NaN");
+    runOpTest(OpType::Mod, "NaN", 5);
+    runOpTest(OpType::Mod, "NaN", -5);
 }
 
 TEST_F(LLVMCodeBuilderTest, Round)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createRound();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createRound();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(4.0, 4.0);
-    addOpTest(3.2, 3.0);
-    addOpTest(3.5, 4.0);
-    addOpTest(3.6, 4.0);
-    addOpTest(-2.4, -2.0);
-    addOpTest(-2.5, -2.0);
-    addOpTest(-2.6, -3.0);
-    addOpTest(-0.4, -0.0);
-    addOpTest(-0.5, -0.0);
-    addOpTest(-0.51, -1.0);
-    addOpTest(inf, inf);
-    addOpTest(-inf, -inf);
-    addOpTest(nan, 0);
+    runUnaryNumOpTest(OpType::Round, 4.0, 4.0);
+    runUnaryNumOpTest(OpType::Round, 3.2, 3.0);
+    runUnaryNumOpTest(OpType::Round, 3.5, 4.0);
+    runUnaryNumOpTest(OpType::Round, 3.6, 4.0);
+    runUnaryNumOpTest(OpType::Round, -2.4, -2.0);
+    runUnaryNumOpTest(OpType::Round, -2.5, -2.0);
+    runUnaryNumOpTest(OpType::Round, -2.6, -3.0);
+    runUnaryNumOpTest(OpType::Round, -0.4, -0.0);
+    runUnaryNumOpTest(OpType::Round, -0.5, -0.0);
+    runUnaryNumOpTest(OpType::Round, -0.51, -1.0);
+    runUnaryNumOpTest(OpType::Round, inf, inf);
+    runUnaryNumOpTest(OpType::Round, -inf, -inf);
+    runUnaryNumOpTest(OpType::Round, nan, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Abs)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createAbs();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createAbs();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(4.0, 4.0);
-    addOpTest(3.2, 3.2);
-    addOpTest(-2.0, 2.0);
-    addOpTest(-2.5, 2.5);
-    addOpTest(-2.6, 2.6);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, 0.0);
-    addOpTest(inf, inf);
-    addOpTest(-inf, inf);
-    addOpTest(nan, 0);
+    runUnaryNumOpTest(OpType::Abs, 4.0, 4.0);
+    runUnaryNumOpTest(OpType::Abs, 3.2, 3.2);
+    runUnaryNumOpTest(OpType::Abs, -2.0, 2.0);
+    runUnaryNumOpTest(OpType::Abs, -2.5, 2.5);
+    runUnaryNumOpTest(OpType::Abs, -2.6, 2.6);
+    runUnaryNumOpTest(OpType::Abs, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Abs, -0.0, 0.0);
+    runUnaryNumOpTest(OpType::Abs, inf, inf);
+    runUnaryNumOpTest(OpType::Abs, -inf, inf);
+    runUnaryNumOpTest(OpType::Abs, nan, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Floor)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createFloor();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createFloor();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(4.0, 4.0);
-    addOpTest(3.2, 3.0);
-    addOpTest(3.5, 3.0);
-    addOpTest(3.6, 3.0);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, -0.0);
-    addOpTest(-2.4, -3.0);
-    addOpTest(-2.5, -3.0);
-    addOpTest(-2.6, -3.0);
-    addOpTest(-0.4, -1.0);
-    addOpTest(-0.5, -1.0);
-    addOpTest(-0.51, -1.0);
-    addOpTest(inf, inf);
-    addOpTest(-inf, -inf);
-    addOpTest(nan, 0);
+    runUnaryNumOpTest(OpType::Floor, 4.0, 4.0);
+    runUnaryNumOpTest(OpType::Floor, 3.2, 3.0);
+    runUnaryNumOpTest(OpType::Floor, 3.5, 3.0);
+    runUnaryNumOpTest(OpType::Floor, 3.6, 3.0);
+    runUnaryNumOpTest(OpType::Floor, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Floor, -0.0, -0.0);
+    runUnaryNumOpTest(OpType::Floor, -2.4, -3.0);
+    runUnaryNumOpTest(OpType::Floor, -2.5, -3.0);
+    runUnaryNumOpTest(OpType::Floor, -2.6, -3.0);
+    runUnaryNumOpTest(OpType::Floor, -0.4, -1.0);
+    runUnaryNumOpTest(OpType::Floor, -0.5, -1.0);
+    runUnaryNumOpTest(OpType::Floor, -0.51, -1.0);
+    runUnaryNumOpTest(OpType::Floor, inf, inf);
+    runUnaryNumOpTest(OpType::Floor, -inf, -inf);
+    runUnaryNumOpTest(OpType::Floor, nan, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Ceil)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createCeil();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createCeil();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(8.0, 8.0);
-    addOpTest(3.2, 4.0);
-    addOpTest(3.5, 4.0);
-    addOpTest(3.6, 4.0);
-    addOpTest(0.4, 1.0);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, -0.0);
-    addOpTest(-2.4, -2.0);
-    addOpTest(-2.5, -2.0);
-    addOpTest(-2.6, -2.0);
-    addOpTest(-0.4, -0.0);
-    addOpTest(-0.5, -0.0);
-    addOpTest(-0.51, -0.0);
-    addOpTest(inf, inf);
-    addOpTest(-inf, -inf);
-    addOpTest(nan, 0);
+    runUnaryNumOpTest(OpType::Ceil, 8.0, 8.0);
+    runUnaryNumOpTest(OpType::Ceil, 3.2, 4.0);
+    runUnaryNumOpTest(OpType::Ceil, 3.5, 4.0);
+    runUnaryNumOpTest(OpType::Ceil, 3.6, 4.0);
+    runUnaryNumOpTest(OpType::Ceil, 0.4, 1.0);
+    runUnaryNumOpTest(OpType::Ceil, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Ceil, -0.0, -0.0);
+    runUnaryNumOpTest(OpType::Ceil, -2.4, -2.0);
+    runUnaryNumOpTest(OpType::Ceil, -2.5, -2.0);
+    runUnaryNumOpTest(OpType::Ceil, -2.6, -2.0);
+    runUnaryNumOpTest(OpType::Ceil, -0.4, -0.0);
+    runUnaryNumOpTest(OpType::Ceil, -0.5, -0.0);
+    runUnaryNumOpTest(OpType::Ceil, -0.51, -0.0);
+    runUnaryNumOpTest(OpType::Ceil, inf, inf);
+    runUnaryNumOpTest(OpType::Ceil, -inf, -inf);
+    runUnaryNumOpTest(OpType::Ceil, nan, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Sqrt)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createSqrt();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createSqrt();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(16.0, 4.0);
-    addOpTest(0.04, 0.2);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, 0.0);
-    addOpTest(-4.0, -nan); // negative NaN shouldn't be a problem
-    addOpTest(inf, inf);
-    addOpTest(-inf, -nan);
-    addOpTest(nan, 0);
+    runUnaryNumOpTest(OpType::Sqrt, 16.0, 4.0);
+    runUnaryNumOpTest(OpType::Sqrt, 0.04, 0.2);
+    runUnaryNumOpTest(OpType::Sqrt, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Sqrt, -0.0, 0.0);
+    runUnaryNumOpTest(OpType::Sqrt, -4.0, -nan); // negative NaN shouldn't be a problem
+    runUnaryNumOpTest(OpType::Sqrt, inf, inf);
+    runUnaryNumOpTest(OpType::Sqrt, -inf, -nan);
+    runUnaryNumOpTest(OpType::Sqrt, nan, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Sin)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createSin();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createSin();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(30.0, 0.5);
-    addOpTest(90.0, 1.0);
-    addOpTest(2.8e-9, 0.0);
-    addOpTest(2.9e-9, 1e-10);
-    addOpTest(570.0, -0.5);
-    addOpTest(-30.0, -0.5);
-    addOpTest(-90.0, -1.0);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, 0.0);
-    addOpTest(inf, -nan); // negative NaN shouldn't be a problem
-    addOpTest(-inf, -nan);
-    addOpTest(nan, 0);
+    runUnaryNumOpTest(OpType::Sin, 30.0, 0.5);
+    runUnaryNumOpTest(OpType::Sin, 90.0, 1.0);
+    runUnaryNumOpTest(OpType::Sin, 2.8e-9, 0.0);
+    runUnaryNumOpTest(OpType::Sin, 2.9e-9, 1e-10);
+    runUnaryNumOpTest(OpType::Sin, 570.0, -0.5);
+    runUnaryNumOpTest(OpType::Sin, -30.0, -0.5);
+    runUnaryNumOpTest(OpType::Sin, -90.0, -1.0);
+    runUnaryNumOpTest(OpType::Sin, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Sin, -0.0, 0.0);
+    runUnaryNumOpTest(OpType::Sin, inf, -nan); // negative NaN shouldn't be a problem
+    runUnaryNumOpTest(OpType::Sin, -inf, -nan);
+    runUnaryNumOpTest(OpType::Sin, nan, 0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Cos)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createCos();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createCos();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(60.0, 0.5);
-    addOpTest(90.0, 0.0);
-    addOpTest(600.0, -0.5);
-    addOpTest(89.9999999971352, 1e-10);
-    addOpTest(89.999999999, 0.0);
-    addOpTest(-60.0, 0.5);
-    addOpTest(-90.0, 0.0);
-    addOpTest(0.0, 1.0);
-    addOpTest(-0.0, 1.0);
-    addOpTest(inf, -nan); // negative NaN shouldn't be a problem
-    addOpTest(-inf, -nan);
-    addOpTest(nan, 1.0);
+    runUnaryNumOpTest(OpType::Cos, 60.0, 0.5);
+    runUnaryNumOpTest(OpType::Cos, 90.0, 0.0);
+    runUnaryNumOpTest(OpType::Cos, 600.0, -0.5);
+    runUnaryNumOpTest(OpType::Cos, 89.9999999971352, 1e-10);
+    runUnaryNumOpTest(OpType::Cos, 89.999999999, 0.0);
+    runUnaryNumOpTest(OpType::Cos, -60.0, 0.5);
+    runUnaryNumOpTest(OpType::Cos, -90.0, 0.0);
+    runUnaryNumOpTest(OpType::Cos, 0.0, 1.0);
+    runUnaryNumOpTest(OpType::Cos, -0.0, 1.0);
+    runUnaryNumOpTest(OpType::Cos, inf, -nan); // negative NaN shouldn't be a problem
+    runUnaryNumOpTest(OpType::Cos, -inf, -nan);
+    runUnaryNumOpTest(OpType::Cos, nan, 1.0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Tan)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createTan();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createTan();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(45.0, 1.0);
-    addOpTest(90.0, inf);
-    addOpTest(270.0, -inf);
-    addOpTest(450.0, inf);
-    addOpTest(-90.0, -inf);
-    addOpTest(-270.0, inf);
-    addOpTest(-450.0, -inf);
-    addOpTest(180.0, 0.0);
-    addOpTest(-180.0, 0.0);
-    addOpTest(2.87e-9, 1e-10);
-    addOpTest(2.8647e-9, 0.0);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, 0.0);
-    addOpTest(inf, -nan); // negative NaN shouldn't be a problem
-    addOpTest(-inf, -nan);
-    addOpTest(nan, 0.0);
+    runUnaryNumOpTest(OpType::Tan, 45.0, 1.0);
+    runUnaryNumOpTest(OpType::Tan, 90.0, inf);
+    runUnaryNumOpTest(OpType::Tan, 270.0, -inf);
+    runUnaryNumOpTest(OpType::Tan, 450.0, inf);
+    runUnaryNumOpTest(OpType::Tan, -90.0, -inf);
+    runUnaryNumOpTest(OpType::Tan, -270.0, inf);
+    runUnaryNumOpTest(OpType::Tan, -450.0, -inf);
+    runUnaryNumOpTest(OpType::Tan, 180.0, 0.0);
+    runUnaryNumOpTest(OpType::Tan, -180.0, 0.0);
+    runUnaryNumOpTest(OpType::Tan, 2.87e-9, 1e-10);
+    runUnaryNumOpTest(OpType::Tan, 2.8647e-9, 0.0);
+    runUnaryNumOpTest(OpType::Tan, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Tan, -0.0, 0.0);
+    runUnaryNumOpTest(OpType::Tan, inf, -nan); // negative NaN shouldn't be a problem
+    runUnaryNumOpTest(OpType::Tan, -inf, -nan);
+    runUnaryNumOpTest(OpType::Tan, nan, 0.0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Asin)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createAsin();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createAsin();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(1.0, 90.0);
-    addOpTest(0.5, 30.0);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, 0.0);
-    addOpTest(-0.5, -30.0);
-    addOpTest(-1.0, -90.0);
-    addOpTest(1.1, nan);
-    addOpTest(-1.2, nan);
-    addOpTest(inf, nan);
-    addOpTest(-inf, nan);
-    addOpTest(nan, 0.0);
+    runUnaryNumOpTest(OpType::Asin, 1.0, 90.0);
+    runUnaryNumOpTest(OpType::Asin, 0.5, 30.0);
+    runUnaryNumOpTest(OpType::Asin, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Asin, -0.0, 0.0);
+    runUnaryNumOpTest(OpType::Asin, -0.5, -30.0);
+    runUnaryNumOpTest(OpType::Asin, -1.0, -90.0);
+    runUnaryNumOpTest(OpType::Asin, 1.1, nan);
+    runUnaryNumOpTest(OpType::Asin, -1.2, nan);
+    runUnaryNumOpTest(OpType::Asin, inf, nan);
+    runUnaryNumOpTest(OpType::Asin, -inf, nan);
+    runUnaryNumOpTest(OpType::Asin, nan, 0.0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Acos)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createAcos();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createAcos();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(1.0, 0.0);
-    addOpTest(0.5, 60.0);
-    addOpTest(0.0, 90.0);
-    addOpTest(-0.0, 90.0);
-    addOpTest(-0.5, 120.0);
-    addOpTest(-1.0, 180.0);
-    addOpTest(1.1, nan);
-    addOpTest(-1.2, nan);
-    addOpTest(inf, nan);
-    addOpTest(-inf, nan);
-    addOpTest(nan, 90.0);
+    runUnaryNumOpTest(OpType::Acos, 1.0, 0.0);
+    runUnaryNumOpTest(OpType::Acos, 0.5, 60.0);
+    runUnaryNumOpTest(OpType::Acos, 0.0, 90.0);
+    runUnaryNumOpTest(OpType::Acos, -0.0, 90.0);
+    runUnaryNumOpTest(OpType::Acos, -0.5, 120.0);
+    runUnaryNumOpTest(OpType::Acos, -1.0, 180.0);
+    runUnaryNumOpTest(OpType::Acos, 1.1, nan);
+    runUnaryNumOpTest(OpType::Acos, -1.2, nan);
+    runUnaryNumOpTest(OpType::Acos, inf, nan);
+    runUnaryNumOpTest(OpType::Acos, -inf, nan);
+    runUnaryNumOpTest(OpType::Acos, nan, 90.0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Atan)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createAtan();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createAtan();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(1.0, 45.0);
-    addOpTest(0.0, 0.0);
-    addOpTest(-0.0, -0.0);
-    addOpTest(-1.0, -45.0);
-    addOpTest(inf, 90.0);
-    addOpTest(-inf, -90.0);
-    addOpTest(nan, 0.0);
+    runUnaryNumOpTest(OpType::Atan, 1.0, 45.0);
+    runUnaryNumOpTest(OpType::Atan, 0.0, 0.0);
+    runUnaryNumOpTest(OpType::Atan, -0.0, -0.0);
+    runUnaryNumOpTest(OpType::Atan, -1.0, -45.0);
+    runUnaryNumOpTest(OpType::Atan, inf, 90.0);
+    runUnaryNumOpTest(OpType::Atan, -inf, -90.0);
+    runUnaryNumOpTest(OpType::Atan, nan, 0.0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Ln)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createLn();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createLn();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(std::exp(1.0), 1.0);
-    addOpTest(std::exp(2.0), 2.0);
-    addOpTest(std::exp(0.3), 0.3);
-    addOpTest(1.0, 0.0);
-    addOpTest(0.0, -inf);
-    addOpTest(-0.0, -inf);
-    addOpTest(-0.7, -nan); // negative NaN shouldn't be a problem
-    addOpTest(inf, inf);
-    addOpTest(-inf, -nan);
-    addOpTest(nan, -inf);
+    runUnaryNumOpTest(OpType::Ln, std::exp(1.0), 1.0);
+    runUnaryNumOpTest(OpType::Ln, std::exp(2.0), 2.0);
+    runUnaryNumOpTest(OpType::Ln, std::exp(0.3), 0.3);
+    runUnaryNumOpTest(OpType::Ln, 1.0, 0.0);
+    runUnaryNumOpTest(OpType::Ln, 0.0, -inf);
+    runUnaryNumOpTest(OpType::Ln, -0.0, -inf);
+    runUnaryNumOpTest(OpType::Ln, -0.7, -nan); // negative NaN shouldn't be a problem
+    runUnaryNumOpTest(OpType::Ln, inf, inf);
+    runUnaryNumOpTest(OpType::Ln, -inf, -nan);
+    runUnaryNumOpTest(OpType::Ln, nan, -inf);
 }
 
 TEST_F(LLVMCodeBuilderTest, Log10)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createLog10();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createLog10();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(10.0, 1.0);
-    addOpTest(1000.0, 3.0);
-    addOpTest(0.01, -2.0);
-    addOpTest(0.0, -inf);
-    addOpTest(-0.0, -inf);
-    addOpTest(-0.7, nan);
-    addOpTest(inf, inf);
-    addOpTest(-inf, nan);
-    addOpTest(nan, -inf);
+    runUnaryNumOpTest(OpType::Log10, 10.0, 1.0);
+    runUnaryNumOpTest(OpType::Log10, 1000.0, 3.0);
+    runUnaryNumOpTest(OpType::Log10, 0.01, -2.0);
+    runUnaryNumOpTest(OpType::Log10, 0.0, -inf);
+    runUnaryNumOpTest(OpType::Log10, -0.0, -inf);
+    runUnaryNumOpTest(OpType::Log10, -0.7, nan);
+    runUnaryNumOpTest(OpType::Log10, inf, inf);
+    runUnaryNumOpTest(OpType::Log10, -inf, nan);
+    runUnaryNumOpTest(OpType::Log10, nan, -inf);
 }
 
 TEST_F(LLVMCodeBuilderTest, Exp)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createExp();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createExp();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(1.0, std::exp(1.0));
-    addOpTest(0.5, std::exp(0.5));
-    addOpTest(0.0, 1.0);
-    addOpTest(-0.0, 1.0);
-    addOpTest(-0.7, std::exp(-0.7));
-    addOpTest(inf, inf);
-    addOpTest(-inf, 0.0);
-    addOpTest(nan, 1.0);
+    runUnaryNumOpTest(OpType::Exp, 1.0, std::exp(1.0));
+    runUnaryNumOpTest(OpType::Exp, 0.5, std::exp(0.5));
+    runUnaryNumOpTest(OpType::Exp, 0.0, 1.0);
+    runUnaryNumOpTest(OpType::Exp, -0.0, 1.0);
+    runUnaryNumOpTest(OpType::Exp, -0.7, std::exp(-0.7));
+    runUnaryNumOpTest(OpType::Exp, inf, inf);
+    runUnaryNumOpTest(OpType::Exp, -inf, 0.0);
+    runUnaryNumOpTest(OpType::Exp, nan, 1.0);
 }
 
 TEST_F(LLVMCodeBuilderTest, Exp10)
 {
-    std::string expected;
-
-    auto addOpTest = [this, &expected](Value v1, double expectedResult) {
-        createBuilder(true);
-
-        m_builder->addConstValue(v1);
-        m_builder->createExp10();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        m_builder->addConstValue(v1);
-        m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-        m_builder->createExp10();
-        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number });
-
-        std::stringstream stream;
-        stream << expectedResult;
-        std::string str = stream.str() + '\n';
-        std::string expected = str + str;
-
-        auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
-
-        testing::internal::CaptureStdout();
-        code->run(ctx.get());
-        const std::string quotes1 = v1.isString() ? "\"" : "";
-        ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1;
-    };
-
     static const double inf = std::numeric_limits<double>::infinity();
     static const double nan = std::numeric_limits<double>::quiet_NaN();
 
-    addOpTest(1.0, 10.0);
-    addOpTest(3.0, 1000.0);
-    addOpTest(0.0, 1.0);
-    addOpTest(-0.0, 1.0);
-    addOpTest(-1.0, 0.1);
-    addOpTest(-5.0, 0.00001);
-    addOpTest(inf, inf);
-    addOpTest(-inf, 0.0);
-    addOpTest(nan, 1.0);
+    runUnaryNumOpTest(OpType::Exp10, 1.0, 10.0);
+    runUnaryNumOpTest(OpType::Exp10, 3.0, 1000.0);
+    runUnaryNumOpTest(OpType::Exp10, 0.0, 1.0);
+    runUnaryNumOpTest(OpType::Exp10, -0.0, 1.0);
+    runUnaryNumOpTest(OpType::Exp10, -1.0, 0.1);
+    runUnaryNumOpTest(OpType::Exp10, -5.0, 0.00001);
+    runUnaryNumOpTest(OpType::Exp10, inf, inf);
+    runUnaryNumOpTest(OpType::Exp10, -inf, 0.0);
+    runUnaryNumOpTest(OpType::Exp10, nan, 1.0);
 }
 
 TEST_F(LLVMCodeBuilderTest, WriteVariable)
@@ -1807,29 +1345,29 @@ TEST_F(LLVMCodeBuilderTest, WriteVariable)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(5);
-    m_builder->createVariableWrite(globalVar1.get());
+    CompilerValue *v = m_builder->addConstValue(5);
+    m_builder->createVariableWrite(globalVar1.get(), v);
 
-    m_builder->addConstValue(-23.5);
-    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-    m_builder->createVariableWrite(globalVar2.get());
+    v = m_builder->addConstValue(-23.5);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->createVariableWrite(globalVar2.get(), v);
 
-    m_builder->addConstValue("test");
-    m_builder->createVariableWrite(globalVar3.get());
+    v = m_builder->addConstValue("test");
+    m_builder->createVariableWrite(globalVar3.get(), v);
 
-    m_builder->addConstValue("abc");
-    m_builder->createVariableWrite(localVar1.get());
+    v = m_builder->addConstValue("abc");
+    m_builder->createVariableWrite(localVar1.get(), v);
 
-    m_builder->addConstValue("hello world");
-    m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String });
-    m_builder->createVariableWrite(localVar1.get());
+    v = m_builder->addConstValue("hello world");
+    v = callConstFuncForType(ValueType::String, v);
+    m_builder->createVariableWrite(localVar1.get(), v);
 
-    m_builder->addConstValue(false);
-    m_builder->createVariableWrite(localVar2.get());
+    v = m_builder->addConstValue(false);
+    m_builder->createVariableWrite(localVar2.get(), v);
 
-    m_builder->addConstValue(true);
-    m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->createVariableWrite(localVar3.get());
+    v = m_builder->addConstValue(true);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->createVariableWrite(localVar3.get(), v);
 
     auto code = m_builder->finalize();
     auto ctx = code->createExecutionContext(&sprite);
@@ -1867,23 +1405,23 @@ TEST_F(LLVMCodeBuilderTest, ReadVariable)
 
     createBuilder(&sprite, true);
 
-    m_builder->addVariableValue(globalVar1.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    CompilerValue *v = m_builder->addVariableValue(globalVar1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(globalVar2.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar2.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(globalVar3.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar3.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar1.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar1.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar2.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar2.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar3.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar3.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     std::string expected;
     expected += globalVar1->value().toString() + '\n';
@@ -2002,11 +1540,11 @@ TEST_F(LLVMCodeBuilderTest, RemoveFromList)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(1);
-    m_builder->createListRemove(globalList.get());
+    CompilerValue *v = m_builder->addConstValue(1);
+    m_builder->createListRemove(globalList.get(), v);
 
-    m_builder->addConstValue(3);
-    m_builder->createListRemove(localList.get());
+    v = m_builder->addConstValue(3);
+    m_builder->createListRemove(localList.get(), v);
 
     auto code = m_builder->finalize();
     auto ctx = code->createExecutionContext(&sprite);
@@ -2044,25 +1582,25 @@ TEST_F(LLVMCodeBuilderTest, AppendToList)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(1);
-    m_builder->createListAppend(globalList.get());
+    CompilerValue *v = m_builder->addConstValue(1);
+    m_builder->createListAppend(globalList.get(), v);
 
-    m_builder->addConstValue("test");
-    m_builder->createListAppend(globalList.get());
+    v = m_builder->addConstValue("test");
+    m_builder->createListAppend(globalList.get(), v);
 
-    m_builder->addConstValue(3);
-    m_builder->createListAppend(localList.get());
+    v = m_builder->addConstValue(3);
+    m_builder->createListAppend(localList.get(), v);
 
     m_builder->createListClear(localList.get());
 
-    m_builder->addConstValue(true);
-    m_builder->createListAppend(localList.get());
+    v = m_builder->addConstValue(true);
+    m_builder->createListAppend(localList.get(), v);
 
-    m_builder->addConstValue(false);
-    m_builder->createListAppend(localList.get());
+    v = m_builder->addConstValue(false);
+    m_builder->createListAppend(localList.get(), v);
 
-    m_builder->addConstValue("hello world");
-    m_builder->createListAppend(localList.get());
+    v = m_builder->addConstValue("hello world");
+    m_builder->createListAppend(localList.get(), v);
 
     auto code = m_builder->finalize();
     auto ctx = code->createExecutionContext(&sprite);
@@ -2100,31 +1638,31 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(2);
-    m_builder->addConstValue(1);
-    m_builder->createListInsert(globalList.get());
+    CompilerValue *v1 = m_builder->addConstValue(2);
+    CompilerValue *v2 = m_builder->addConstValue(1);
+    m_builder->createListInsert(globalList.get(), v1, v2);
 
-    m_builder->addConstValue(3);
-    m_builder->addConstValue("test");
-    m_builder->createListInsert(globalList.get());
+    v1 = m_builder->addConstValue(3);
+    v2 = m_builder->addConstValue("test");
+    m_builder->createListInsert(globalList.get(), v1, v2);
 
-    m_builder->addConstValue(0);
-    m_builder->addConstValue(3);
-    m_builder->createListInsert(localList.get());
+    v1 = m_builder->addConstValue(0);
+    v2 = m_builder->addConstValue(3);
+    m_builder->createListInsert(localList.get(), v1, v2);
 
     m_builder->createListClear(localList.get());
 
-    m_builder->addConstValue(0);
-    m_builder->addConstValue(true);
-    m_builder->createListInsert(localList.get());
+    v1 = m_builder->addConstValue(0);
+    v2 = m_builder->addConstValue(true);
+    m_builder->createListInsert(localList.get(), v1, v2);
 
-    m_builder->addConstValue(0);
-    m_builder->addConstValue(false);
-    m_builder->createListInsert(localList.get());
+    v1 = m_builder->addConstValue(0);
+    v2 = m_builder->addConstValue(false);
+    m_builder->createListInsert(localList.get(), v1, v2);
 
-    m_builder->addConstValue(1);
-    m_builder->addConstValue("hello world");
-    m_builder->createListInsert(localList.get());
+    v1 = m_builder->addConstValue(1);
+    v2 = m_builder->addConstValue("hello world");
+    m_builder->createListInsert(localList.get(), v1, v2);
 
     auto code = m_builder->finalize();
     auto ctx = code->createExecutionContext(&sprite);
@@ -2162,25 +1700,25 @@ TEST_F(LLVMCodeBuilderTest, ListReplace)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(2);
-    m_builder->addConstValue(1);
-    m_builder->createListReplace(globalList.get());
+    CompilerValue *v1 = m_builder->addConstValue(2);
+    CompilerValue *v2 = m_builder->addConstValue(1);
+    m_builder->createListReplace(globalList.get(), v1, v2);
 
-    m_builder->addConstValue(1);
-    m_builder->addConstValue("test");
-    m_builder->createListReplace(globalList.get());
+    v1 = m_builder->addConstValue(1);
+    v2 = m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get(), v1, v2);
 
-    m_builder->addConstValue(0);
-    m_builder->addConstValue(3);
-    m_builder->createListReplace(localList.get());
+    v1 = m_builder->addConstValue(0);
+    v2 = m_builder->addConstValue(3);
+    m_builder->createListReplace(localList.get(), v1, v2);
 
-    m_builder->addConstValue(2);
-    m_builder->addConstValue(true);
-    m_builder->createListReplace(localList.get());
+    v1 = m_builder->addConstValue(2);
+    v2 = m_builder->addConstValue(true);
+    m_builder->createListReplace(localList.get(), v1, v2);
 
-    m_builder->addConstValue(3);
-    m_builder->addConstValue("hello world");
-    m_builder->createListReplace(localList.get());
+    v1 = m_builder->addConstValue(3);
+    v2 = m_builder->addConstValue("hello world");
+    m_builder->createListReplace(localList.get(), v1, v2);
 
     auto code = m_builder->finalize();
     auto ctx = code->createExecutionContext(&sprite);
@@ -2218,29 +1756,29 @@ TEST_F(LLVMCodeBuilderTest, GetListItem)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(2);
-    m_builder->addListItem(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    CompilerValue *v = m_builder->addConstValue(2);
+    v = m_builder->addListItem(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addConstValue("test");
-    m_builder->createListReplace(globalList.get());
+    CompilerValue *v1 = m_builder->addConstValue(1);
+    CompilerValue *v2 = m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get(), v1, v2);
 
-    m_builder->addConstValue(0);
-    m_builder->addListItem(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(0);
+    v = m_builder->addListItem(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addListItem(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(0);
+    v = m_builder->addListItem(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(2);
-    m_builder->addListItem(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(2);
+    v = m_builder->addListItem(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(3);
-    m_builder->addListItem(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(3);
+    v = m_builder->addListItem(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     static const std::string expected =
         "3\n"
@@ -2284,11 +1822,11 @@ TEST_F(LLVMCodeBuilderTest, GetListSize)
 
     createBuilder(&sprite, true);
 
-    m_builder->addListSize(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    CompilerValue *v = m_builder->addListSize(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addListSize(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addListSize(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     static const std::string expected =
         "3\n"
@@ -2329,49 +1867,49 @@ TEST_F(LLVMCodeBuilderTest, GetListItemIndex)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(2);
-    m_builder->addListItemIndex(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    CompilerValue *v = m_builder->addConstValue(2);
+    v = m_builder->addListItemIndex(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addListItemIndex(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(1);
+    v = m_builder->addListItemIndex(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addListItemIndex(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(0);
+    v = m_builder->addListItemIndex(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addConstValue("test");
-    m_builder->createListReplace(globalList.get());
+    CompilerValue *v1 = m_builder->addConstValue(1);
+    CompilerValue *v2 = m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get(), v1, v2);
 
-    m_builder->addConstValue(2);
-    m_builder->addListItemIndex(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(2);
+    v = m_builder->addListItemIndex(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addListItemIndex(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(1);
+    v = m_builder->addListItemIndex(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("test");
-    m_builder->addListItemIndex(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("test");
+    v = m_builder->addListItemIndex(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("abc");
-    m_builder->addListItemIndex(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("abc");
+    v = m_builder->addListItemIndex(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("doLor");
-    m_builder->addListItemIndex(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("doLor");
+    v = m_builder->addListItemIndex(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(true);
-    m_builder->addListItemIndex(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(true);
+    v = m_builder->addListItemIndex(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("site");
-    m_builder->addListItemIndex(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("site");
+    v = m_builder->addListItemIndex(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     static const std::string expected =
         "1\n"
@@ -2420,49 +1958,49 @@ TEST_F(LLVMCodeBuilderTest, ListContainsItem)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(2);
-    m_builder->addListContains(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    CompilerValue *v = m_builder->addConstValue(2);
+    v = m_builder->addListContains(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addListContains(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(1);
+    v = m_builder->addListContains(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addListContains(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(0);
+    v = m_builder->addListContains(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addConstValue("test");
-    m_builder->createListReplace(globalList.get());
+    CompilerValue *v1 = m_builder->addConstValue(1);
+    CompilerValue *v2 = m_builder->addConstValue("test");
+    m_builder->createListReplace(globalList.get(), v1, v2);
 
-    m_builder->addConstValue(2);
-    m_builder->addListContains(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(2);
+    v = m_builder->addListContains(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addListContains(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(1);
+    v = m_builder->addListContains(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("test");
-    m_builder->addListContains(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("test");
+    v = m_builder->addListContains(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("abc");
-    m_builder->addListContains(globalList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("abc");
+    v = m_builder->addListContains(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("doLor");
-    m_builder->addListContains(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("doLor");
+    v = m_builder->addListContains(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(true);
-    m_builder->addListContains(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(true);
+    v = m_builder->addListContains(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue("site");
-    m_builder->addListContains(localList.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("site");
+    v = m_builder->addListContains(localList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     static const std::string expected =
         "true\n"
@@ -2489,24 +2027,28 @@ TEST_F(LLVMCodeBuilderTest, ListContainsItem)
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
-        m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+        m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
 
-        m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {});
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        CompilerValue *v = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         m_builder->yield();
 
-        m_builder->addConstValue("1");
-        m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String });
-        m_builder->addConstValue("2");
-        m_builder->addConstValue(3);
-        m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String });
-
-        m_builder->addConstValue("test");
-        m_builder->addConstValue("4");
-        m_builder->addConstValue("5");
-        m_builder->addFunctionCall("test_function_3_args_ret", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String });
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addConstValue("1");
+        v = m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v });
+        CompilerValue *v1 = m_builder->addConstValue("2");
+        CompilerValue *v2 = m_builder->addConstValue(3);
+        m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 });
+
+        v = m_builder->addConstValue("test");
+        v1 = m_builder->addConstValue("4");
+        v2 = m_builder->addConstValue("5");
+        v = m_builder->addFunctionCall(
+            "test_function_3_args_ret",
+            Compiler::StaticType::String,
+            { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String },
+            { v, v1, v2 });
+        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     };
 
     // Without warp
@@ -2576,19 +2118,19 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
 
     createBuilder(&sprite, false);
 
-    m_builder->addConstValue(12.5);
-    m_builder->createVariableWrite(globalVar.get());
+    CompilerValue *v = m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get(), v);
 
-    m_builder->addConstValue(true);
-    m_builder->createVariableWrite(localVar.get());
+    v = m_builder->addConstValue(true);
+    m_builder->createVariableWrite(localVar.get(), v);
 
     m_builder->yield();
 
-    m_builder->addVariableValue(globalVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     std::string expected =
         "hello world\n"
@@ -2634,67 +2176,44 @@ TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend)
     m_builder->createListClear(localList1.get());
     m_builder->createListClear(localList2.get());
 
-    m_builder->addConstValue(1);
-    m_builder->createListAppend(globalList1.get());
-    m_builder->addConstValue(2);
-    m_builder->createListAppend(globalList1.get());
-    m_builder->addConstValue(3);
-    m_builder->createListAppend(globalList1.get());
-
-    m_builder->addConstValue(1);
-    m_builder->createListAppend(globalList2.get());
-    m_builder->addConstValue(2);
-    m_builder->createListAppend(globalList2.get());
-    m_builder->addConstValue(3);
-    m_builder->createListAppend(globalList2.get());
-
-    m_builder->addConstValue(1);
-    m_builder->addConstValue(12.5);
-    m_builder->createListReplace(globalList2.get());
-
-    m_builder->addConstValue(0);
-    m_builder->addConstValue("Lorem");
-    m_builder->createListInsert(localList1.get());
-    m_builder->addConstValue(0);
-    m_builder->addConstValue("ipsum");
-    m_builder->createListInsert(localList1.get());
-
-    m_builder->addConstValue(0);
-    m_builder->addConstValue(true);
-    m_builder->createListInsert(localList2.get());
-    m_builder->addConstValue(0);
-    m_builder->addConstValue(false);
-    m_builder->createListInsert(localList2.get());
+    m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1));
+    m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2));
+    m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(3));
+
+    m_builder->createListAppend(globalList2.get(), m_builder->addConstValue(1));
+    m_builder->createListAppend(globalList2.get(), m_builder->addConstValue(2));
+    m_builder->createListAppend(globalList2.get(), m_builder->addConstValue(3));
+
+    m_builder->createListReplace(globalList2.get(), m_builder->addConstValue(1), m_builder->addConstValue(12.5));
+
+    m_builder->createListInsert(localList1.get(), m_builder->addConstValue(0), m_builder->addConstValue("Lorem"));
+    m_builder->createListInsert(localList1.get(), m_builder->addConstValue(0), m_builder->addConstValue("ipsum"));
+
+    m_builder->createListInsert(localList2.get(), m_builder->addConstValue(0), m_builder->addConstValue(true));
+    m_builder->createListInsert(localList2.get(), m_builder->addConstValue(0), m_builder->addConstValue(false));
 
     m_builder->yield();
 
-    m_builder->addConstValue(1);
-    m_builder->addListItem(globalList1.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(1));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(2);
-    m_builder->addListItem(globalList1.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(2));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addListItem(globalList2.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addListItem(globalList2.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(0));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addListItem(localList1.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addListItem(localList1.get(), m_builder->addConstValue(0));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(1);
-    m_builder->addListItem(localList1.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addListItem(localList1.get(), m_builder->addConstValue(1));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->addListItem(localList2.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addListItem(localList2.get(), m_builder->addConstValue(0));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     std::string expected =
         "2\n"
@@ -2725,96 +2244,96 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     createBuilder(true);
 
     // Without else branch (const condition)
-    m_builder->addConstValue("true");
-    m_builder->beginIfStatement();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    CompilerValue *v = m_builder->addConstValue("true");
+    m_builder->beginIfStatement(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endIf();
 
-    m_builder->addConstValue("false");
-    m_builder->beginIfStatement();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue("false");
+    m_builder->beginIfStatement(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endIf();
 
     // Without else branch (condition returned by function)
-    m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {});
-    m_builder->addConstValue("no_args_output");
-    m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String });
-    m_builder->beginIfStatement();
-    m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    CompilerValue *v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    CompilerValue *v2 = m_builder->addConstValue("no_args_output");
+    v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
+    m_builder->beginIfStatement(v);
+    v = m_builder->addConstValue(0);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
-    m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {});
-    m_builder->addConstValue("");
-    m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String });
-    m_builder->beginIfStatement();
-    m_builder->addConstValue(1);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    v2 = m_builder->addConstValue("");
+    v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
+    m_builder->beginIfStatement(v);
+    v = m_builder->addConstValue(1);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
     // With else branch (const condition)
-    m_builder->addConstValue("true");
-    m_builder->beginIfStatement();
-    m_builder->addConstValue(2);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("true");
+    m_builder->beginIfStatement(v);
+    v = m_builder->addConstValue(2);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
-    m_builder->addConstValue(3);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(3);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
-    m_builder->addConstValue("false");
-    m_builder->beginIfStatement();
-    m_builder->addConstValue(4);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("false");
+    m_builder->beginIfStatement(v);
+    v = m_builder->addConstValue(4);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
-    m_builder->addConstValue(5);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(5);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
     // With else branch (condition returned by function)
-    m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {});
-    m_builder->addConstValue("no_args_output");
-    m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String });
-    m_builder->beginIfStatement();
-    m_builder->addConstValue(6);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    v2 = m_builder->addConstValue("no_args_output");
+    v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
+    m_builder->beginIfStatement(v);
+    v = m_builder->addConstValue(6);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
-    m_builder->addConstValue(7);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(7);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
-    m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {});
-    m_builder->addConstValue("");
-    m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String });
-    m_builder->beginIfStatement();
-    m_builder->addConstValue(8);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    v2 = m_builder->addConstValue("");
+    v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
+    m_builder->beginIfStatement(v);
+    v = m_builder->addConstValue(8);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
-    m_builder->addConstValue(9);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue(9);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
     // Nested 1
-    m_builder->addConstValue(true);
-    m_builder->beginIfStatement();
+    v = m_builder->addConstValue(true);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue(false);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(false);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue(0);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(0);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         {
-            m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(1);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-            m_builder->addConstValue(false);
-            m_builder->beginIfStatement();
+            v = m_builder->addConstValue(false);
+            m_builder->beginIfStatement(v);
             m_builder->beginElseBranch();
             {
-                m_builder->addConstValue(2);
-                m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+                v = m_builder->addConstValue(2);
+                m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
             }
             m_builder->endIf();
         }
@@ -2822,45 +2341,45 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     }
     m_builder->beginElseBranch();
     {
-        m_builder->addConstValue(true);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(true);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue(3);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(3);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         {
-            m_builder->addConstValue(4);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(4);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endIf();
     }
     m_builder->endIf();
 
     // Nested 2
-    m_builder->addConstValue(false);
-    m_builder->beginIfStatement();
+    v = m_builder->addConstValue(false);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue(false);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(false);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue(5);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(5);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         {
-            m_builder->addConstValue(6);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(6);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endIf();
     }
     m_builder->beginElseBranch();
     {
-        m_builder->addConstValue(true);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(true);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue(7);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(7);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         m_builder->endIf();
@@ -2907,69 +2426,69 @@ TEST_F(LLVMCodeBuilderTest, IfStatementVariables)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(12.5);
-    m_builder->createVariableWrite(globalVar.get());
+    CompilerValue *v = m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get(), v);
 
-    m_builder->addConstValue(true);
-    m_builder->createVariableWrite(localVar.get());
+    v = m_builder->addConstValue(true);
+    m_builder->createVariableWrite(localVar.get(), v);
 
-    m_builder->addConstValue(true);
-    m_builder->beginIfStatement();
+    v = m_builder->addConstValue(true);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createVariableWrite(globalVar.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get(), v);
     }
     m_builder->endIf();
 
-    m_builder->addVariableValue(globalVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(12.5);
-    m_builder->createVariableWrite(globalVar.get());
+    v = m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get(), v);
 
-    m_builder->addConstValue(false);
-    m_builder->beginIfStatement();
+    v = m_builder->addConstValue(false);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createVariableWrite(globalVar.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get(), v);
 
-        m_builder->addConstValue(0);
-        m_builder->createVariableWrite(localVar.get());
+        v = m_builder->addConstValue(0);
+        m_builder->createVariableWrite(localVar.get(), v);
     }
     m_builder->endIf();
 
-    m_builder->addVariableValue(globalVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(true);
-    m_builder->beginIfStatement();
+    v = m_builder->addConstValue(true);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue(true);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(true);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue(true);
-            m_builder->createVariableWrite(globalVar.get());
+            v = m_builder->addConstValue(true);
+            m_builder->createVariableWrite(globalVar.get(), v);
         }
         m_builder->endIf();
 
-        m_builder->addConstValue(false);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(false);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue(-8.2);
-            m_builder->createVariableWrite(localVar.get());
+            v = m_builder->addConstValue(-8.2);
+            m_builder->createVariableWrite(localVar.get(), v);
         }
         m_builder->endIf();
     }
     m_builder->endIf();
 
-    m_builder->addVariableValue(globalVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     std::string expected =
         "hello world\n"
@@ -3009,52 +2528,44 @@ TEST_F(LLVMCodeBuilderTest, IfStatementLists)
         m_builder->createListClear(globalList2.get());
         m_builder->createListClear(localList.get());
 
-        m_builder->addConstValue(1);
-        m_builder->createListAppend(globalList1.get());
-        m_builder->addConstValue(2);
-        m_builder->createListAppend(globalList1.get());
+        m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1));
+        m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2));
 
-        m_builder->addConstValue("hello");
-        m_builder->createListAppend(globalList2.get());
-        m_builder->addConstValue("world");
-        m_builder->createListAppend(globalList2.get());
+        m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("hello"));
+        m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("world"));
 
-        m_builder->addConstValue(false);
-        m_builder->createListAppend(localList.get());
-        m_builder->addConstValue(true);
-        m_builder->createListAppend(localList.get());
+        m_builder->createListAppend(localList.get(), m_builder->addConstValue(false));
+        m_builder->createListAppend(localList.get(), m_builder->addConstValue(true));
     };
 
     auto checkLists = [this, globalList1, globalList2, localList]() {
-        m_builder->addConstValue(0);
-        m_builder->addListItem(globalList1.get());
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(0));
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-        m_builder->addConstValue(1);
-        m_builder->addListItem(globalList2.get());
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1));
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-        m_builder->addConstValue(0);
-        m_builder->addListItem(localList.get());
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0));
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     };
 
     // if (true)
     resetLists();
 
-    m_builder->addConstValue(true);
-    m_builder->beginIfStatement();
+    CompilerValue *v, *v1, *v2;
+    v = m_builder->addConstValue(true);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createListAppend(globalList1.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get(), v);
 
-        m_builder->addConstValue(0);
-        m_builder->addConstValue(8.5);
-        m_builder->createListInsert(globalList2.get());
+        v1 = m_builder->addConstValue(0);
+        v2 = m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get(), v1, v2);
 
-        m_builder->addConstValue(1);
-        m_builder->addConstValue(-4.25);
-        m_builder->createListReplace(localList.get());
+        v1 = m_builder->addConstValue(1);
+        v2 = m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get(), v1, v2);
     }
     m_builder->endIf();
 
@@ -3063,19 +2574,19 @@ TEST_F(LLVMCodeBuilderTest, IfStatementLists)
     // if (false)
     resetLists();
 
-    m_builder->addConstValue(false);
-    m_builder->beginIfStatement();
+    v = m_builder->addConstValue(false);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createListAppend(globalList1.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get(), v);
 
-        m_builder->addConstValue(0);
-        m_builder->addConstValue(8.5);
-        m_builder->createListInsert(globalList2.get());
+        v1 = m_builder->addConstValue(0);
+        v2 = m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get(), v1, v2);
 
-        m_builder->addConstValue(1);
-        m_builder->addConstValue(-4.25);
-        m_builder->createListReplace(localList.get());
+        v1 = m_builder->addConstValue(1);
+        v2 = m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get(), v1, v2);
     }
     m_builder->endIf();
 
@@ -3084,27 +2595,27 @@ TEST_F(LLVMCodeBuilderTest, IfStatementLists)
     // if (true) { if (true) { ... }; if (false) { ... } }
     resetLists();
 
-    m_builder->addConstValue(true);
-    m_builder->beginIfStatement();
+    v = m_builder->addConstValue(true);
+    m_builder->beginIfStatement(v);
     {
-        m_builder->addConstValue(true);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(true);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue("hello world");
-            m_builder->createListAppend(globalList1.get());
+            v = m_builder->addConstValue("hello world");
+            m_builder->createListAppend(globalList1.get(), v);
 
-            m_builder->addConstValue(0);
-            m_builder->addConstValue(8.5);
-            m_builder->createListInsert(globalList2.get());
+            v1 = m_builder->addConstValue(0);
+            v2 = m_builder->addConstValue(8.5);
+            m_builder->createListInsert(globalList2.get(), v1, v2);
         }
         m_builder->endIf();
 
-        m_builder->addConstValue(false);
-        m_builder->beginIfStatement();
+        v = m_builder->addConstValue(false);
+        m_builder->beginIfStatement(v);
         {
-            m_builder->addConstValue(1);
-            m_builder->addConstValue(-4.25);
-            m_builder->createListReplace(localList.get());
+            v1 = m_builder->addConstValue(1);
+            v2 = m_builder->addConstValue(-4.25);
+            m_builder->createListReplace(localList.get(), v1, v2);
         }
         m_builder->endIf();
     }
@@ -3135,60 +2646,60 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     createBuilder(true);
 
     // Const count
-    m_builder->addConstValue("-5");
-    m_builder->beginRepeatLoop();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    CompilerValue *v = m_builder->addConstValue("-5");
+    m_builder->beginRepeatLoop(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
-    m_builder->addConstValue(0);
-    m_builder->beginRepeatLoop();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(0);
+    m_builder->beginRepeatLoop(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
-    m_builder->addConstValue(3);
-    m_builder->beginRepeatLoop();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(3);
+    m_builder->beginRepeatLoop(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
-    m_builder->addConstValue("2.4");
-    m_builder->beginRepeatLoop();
-    m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("2.4");
+    m_builder->beginRepeatLoop(v);
+    v = m_builder->addConstValue(0);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endLoop();
 
-    m_builder->addConstValue("2.5");
-    m_builder->beginRepeatLoop();
-    m_builder->addConstValue(1);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addConstValue("2.5");
+    m_builder->beginRepeatLoop(v);
+    v = m_builder->addConstValue(1);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endLoop();
 
     // Count returned by function
-    m_builder->addConstValue(2);
-    m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number });
-    m_builder->beginRepeatLoop();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(2);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->beginRepeatLoop(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     // Nested
-    m_builder->addConstValue(2);
-    m_builder->beginRepeatLoop();
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
     {
-        m_builder->addConstValue(2);
-        m_builder->beginRepeatLoop();
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
         {
-            m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(1);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endLoop();
 
-        m_builder->addConstValue(2);
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addConstValue(2);
+        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-        m_builder->addConstValue(3);
-        m_builder->beginRepeatLoop();
+        v = m_builder->addConstValue(3);
+        m_builder->beginRepeatLoop(v);
         {
-            m_builder->addConstValue(3);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+            v = m_builder->addConstValue(3);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endLoop();
     }
@@ -3229,9 +2740,9 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     // Yield
     createBuilder(false);
 
-    m_builder->addConstValue(3);
-    m_builder->beginRepeatLoop();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(3);
+    m_builder->beginRepeatLoop(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     code = m_builder->finalize();
@@ -3256,8 +2767,8 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     // No warp no-op loop
     createBuilder(false);
 
-    m_builder->addConstValue(0); // don't yield
-    m_builder->beginRepeatLoop();
+    v = m_builder->addConstValue(0); // don't yield
+    m_builder->beginRepeatLoop(v);
     m_builder->endLoop();
 
     code = m_builder->finalize();
@@ -3272,57 +2783,57 @@ TEST_F(LLVMCodeBuilderTest, WhileLoop)
 
     // Const condition
     m_builder->beginLoopCondition();
-    m_builder->addConstValue("false");
-    m_builder->beginWhileLoop();
-    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {});
+    CompilerValue *v = m_builder->addConstValue("false");
+    m_builder->beginWhileLoop(v);
+    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     m_builder->beginLoopCondition();
-    m_builder->addConstValue(false);
-    m_builder->beginWhileLoop();
-    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(false);
+    m_builder->beginWhileLoop(v);
+    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     // Condition returned by function
-    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {});
+    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {});
     m_builder->beginLoopCondition();
-    m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {});
-    m_builder->addConstValue(2);
-    m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number });
-    m_builder->beginWhileLoop();
-    m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
-    m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {});
+    CompilerValue *v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {});
+    CompilerValue *v2 = m_builder->addConstValue(2);
+    v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 });
+    m_builder->beginWhileLoop(v);
+    v = m_builder->addConstValue(0);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     // Nested
-    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {});
+    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {});
     m_builder->beginLoopCondition();
-    m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {});
-    m_builder->addConstValue(3);
-    m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number });
-    m_builder->beginWhileLoop();
+    v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {});
+    v2 = m_builder->addConstValue(3);
+    v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 });
+    m_builder->beginWhileLoop(v);
     {
         m_builder->beginLoopCondition();
-        m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {});
-        m_builder->addConstValue(3);
-        m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number });
-        m_builder->beginWhileLoop();
+        v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {});
+        v2 = m_builder->addConstValue(3);
+        v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 });
+        m_builder->beginWhileLoop(v);
         {
-            m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
-            m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {});
+            v = m_builder->addConstValue(1);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
         }
         m_builder->endLoop();
 
-        m_builder->addConstValue(2);
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addConstValue(2);
+        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         m_builder->beginLoopCondition();
-        m_builder->addConstValue(false);
-        m_builder->beginWhileLoop();
+        v = m_builder->addConstValue(false);
+        m_builder->beginWhileLoop(v);
         {
-            m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {});
+            m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {});
         }
         m_builder->endLoop();
     }
@@ -3348,9 +2859,9 @@ TEST_F(LLVMCodeBuilderTest, WhileLoop)
     createBuilder(false);
 
     m_builder->beginLoopCondition();
-    m_builder->addConstValue(true);
-    m_builder->beginWhileLoop();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(true);
+    m_builder->beginWhileLoop(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     code = m_builder->finalize();
@@ -3374,60 +2885,60 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
 
     // Const condition
     m_builder->beginLoopCondition();
-    m_builder->addConstValue("true");
-    m_builder->beginRepeatUntilLoop();
-    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {});
+    CompilerValue *v = m_builder->addConstValue("true");
+    m_builder->beginRepeatUntilLoop(v);
+    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     m_builder->beginLoopCondition();
-    m_builder->addConstValue(true);
-    m_builder->beginRepeatUntilLoop();
-    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(true);
+    m_builder->beginRepeatUntilLoop(v);
+    m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     // Condition returned by function
-    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {});
+    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {});
     m_builder->beginLoopCondition();
-    m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {});
-    m_builder->addConstValue(2);
-    m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number });
-    m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->beginRepeatUntilLoop();
-    m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
-    m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {});
+    CompilerValue *v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {});
+    CompilerValue *v2 = m_builder->addConstValue(2);
+    v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 });
+    v = m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v });
+    m_builder->beginRepeatUntilLoop(v);
+    v = m_builder->addConstValue(0);
+    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     // Nested
-    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {});
+    m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {});
     m_builder->beginLoopCondition();
-    m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {});
-    m_builder->addConstValue(3);
-    m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number });
-    m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-    m_builder->beginRepeatUntilLoop();
+    v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {});
+    v2 = m_builder->addConstValue(3);
+    v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 });
+    v = m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v });
+    m_builder->beginRepeatUntilLoop(v);
     {
         m_builder->beginLoopCondition();
-        m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {});
-        m_builder->addConstValue(3);
-        m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number });
-        m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool });
-        m_builder->beginRepeatUntilLoop();
+        v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {});
+        v2 = m_builder->addConstValue(3);
+        v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 });
+        v = m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v });
+        m_builder->beginRepeatUntilLoop(v);
         {
-            m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
-            m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {});
+            v = m_builder->addConstValue(1);
+            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
         }
         m_builder->endLoop();
 
-        m_builder->addConstValue(2);
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addConstValue(2);
+        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         m_builder->beginLoopCondition();
-        m_builder->addConstValue(true);
-        m_builder->beginRepeatUntilLoop();
+        v = m_builder->addConstValue(true);
+        m_builder->beginRepeatUntilLoop(v);
         {
-            m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {});
+            m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {});
         }
         m_builder->endLoop();
     }
@@ -3453,9 +2964,9 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
     createBuilder(false);
 
     m_builder->beginLoopCondition();
-    m_builder->addConstValue(false);
-    m_builder->beginRepeatUntilLoop();
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {});
+    v = m_builder->addConstValue(false);
+    m_builder->beginRepeatUntilLoop(v);
+    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     code = m_builder->finalize();
@@ -3493,101 +3004,101 @@ TEST_F(LLVMCodeBuilderTest, LoopVariables)
 
     createBuilder(&sprite, true);
 
-    m_builder->addConstValue(12.5);
-    m_builder->createVariableWrite(globalVar.get());
+    CompilerValue *v = m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get(), v);
 
-    m_builder->addConstValue(true);
-    m_builder->createVariableWrite(localVar.get());
+    v = m_builder->addConstValue(true);
+    m_builder->createVariableWrite(localVar.get(), v);
 
-    m_builder->addConstValue(2);
-    m_builder->beginRepeatLoop();
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createVariableWrite(globalVar.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get(), v);
     }
     m_builder->endLoop();
 
-    m_builder->addVariableValue(globalVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(12.5);
-    m_builder->createVariableWrite(globalVar.get());
+    v = m_builder->addConstValue(12.5);
+    m_builder->createVariableWrite(globalVar.get(), v);
 
-    m_builder->addConstValue(0);
-    m_builder->beginRepeatLoop();
+    v = m_builder->addConstValue(0);
+    m_builder->beginRepeatLoop(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createVariableWrite(globalVar.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createVariableWrite(globalVar.get(), v);
 
-        m_builder->addConstValue(0);
-        m_builder->createVariableWrite(localVar.get());
+        v = m_builder->addConstValue(0);
+        m_builder->createVariableWrite(localVar.get(), v);
     }
     m_builder->endLoop();
 
-    m_builder->addVariableValue(globalVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addConstValue(0);
-    m_builder->createVariableWrite(counter1.get());
+    v = m_builder->addConstValue(0);
+    m_builder->createVariableWrite(counter1.get(), v);
 
     m_builder->beginLoopCondition();
-    m_builder->addVariableValue(counter1.get());
-    m_builder->addConstValue(5);
-    m_builder->createCmpLT();
-    m_builder->beginWhileLoop();
+    CompilerValue *v1 = m_builder->addVariableValue(counter1.get());
+    CompilerValue *v2 = m_builder->addConstValue(5);
+    v = m_builder->createCmpLT(v1, v2);
+    m_builder->beginWhileLoop(v);
     {
-        m_builder->addConstValue(0);
-        m_builder->createVariableWrite(counter2.get());
+        v = m_builder->addConstValue(0);
+        m_builder->createVariableWrite(counter2.get(), v);
 
         m_builder->beginLoopCondition();
-        m_builder->addVariableValue(counter2.get());
-        m_builder->addConstValue(3);
-        m_builder->createCmpEQ();
-        m_builder->beginRepeatUntilLoop();
+        v1 = m_builder->addVariableValue(counter2.get());
+        v2 = m_builder->addConstValue(3);
+        v = m_builder->createCmpEQ(v1, v2);
+        m_builder->beginRepeatUntilLoop(v);
         {
-            m_builder->addConstValue(true);
-            m_builder->createVariableWrite(globalVar.get());
+            v = m_builder->addConstValue(true);
+            m_builder->createVariableWrite(globalVar.get(), v);
 
-            m_builder->addVariableValue(counter2.get());
-            m_builder->addConstValue(1);
-            m_builder->createAdd();
-            m_builder->createVariableWrite(counter2.get());
+            v1 = m_builder->addVariableValue(counter2.get());
+            v2 = m_builder->addConstValue(1);
+            v = m_builder->createAdd(v1, v2);
+            m_builder->createVariableWrite(counter2.get(), v);
         }
         m_builder->endLoop();
 
         m_builder->beginLoopCondition();
-        m_builder->addConstValue(false);
-        m_builder->beginWhileLoop();
+        v = m_builder->addConstValue(false);
+        m_builder->beginWhileLoop(v);
         {
-            m_builder->addConstValue(-8.2);
-            m_builder->createVariableWrite(localVar.get());
+            v = m_builder->addConstValue(-8.2);
+            m_builder->createVariableWrite(localVar.get(), v);
         }
         m_builder->endLoop();
 
-        m_builder->addVariableValue(counter1.get());
-        m_builder->addConstValue(1);
-        m_builder->createAdd();
-        m_builder->createVariableWrite(counter1.get());
+        v1 = m_builder->addVariableValue(counter1.get());
+        v2 = m_builder->addConstValue(1);
+        v = m_builder->createAdd(v1, v2);
+        m_builder->createVariableWrite(counter1.get(), v);
     }
     m_builder->endLoop();
 
     m_builder->beginLoopCondition();
-    m_builder->addConstValue(true);
-    m_builder->beginRepeatUntilLoop();
+    v = m_builder->addConstValue(true);
+    m_builder->beginRepeatUntilLoop(v);
     {
-        m_builder->addConstValue(-8.2);
-        m_builder->createVariableWrite(localVar.get());
+        v = m_builder->addConstValue(-8.2);
+        m_builder->createVariableWrite(localVar.get(), v);
     }
     m_builder->endLoop();
 
-    m_builder->addVariableValue(globalVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-    m_builder->addVariableValue(localVar.get());
-    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+    v = m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     std::string expected =
         "hello world\n"
@@ -3632,52 +3143,44 @@ TEST_F(LLVMCodeBuilderTest, LoopLists)
         m_builder->createListClear(globalList2.get());
         m_builder->createListClear(localList.get());
 
-        m_builder->addConstValue(1);
-        m_builder->createListAppend(globalList1.get());
-        m_builder->addConstValue(2);
-        m_builder->createListAppend(globalList1.get());
+        m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1));
+        m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2));
 
-        m_builder->addConstValue("hello");
-        m_builder->createListAppend(globalList2.get());
-        m_builder->addConstValue("world");
-        m_builder->createListAppend(globalList2.get());
+        m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("hello"));
+        m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("world"));
 
-        m_builder->addConstValue(false);
-        m_builder->createListAppend(localList.get());
-        m_builder->addConstValue(true);
-        m_builder->createListAppend(localList.get());
+        m_builder->createListAppend(localList.get(), m_builder->addConstValue(false));
+        m_builder->createListAppend(localList.get(), m_builder->addConstValue(true));
     };
 
     auto checkLists = [this, globalList1, globalList2, localList]() {
-        m_builder->addConstValue(0);
-        m_builder->addListItem(globalList1.get());
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(0));
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-        m_builder->addConstValue(1);
-        m_builder->addListItem(globalList2.get());
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1));
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
-        m_builder->addConstValue(0);
-        m_builder->addListItem(localList.get());
-        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String });
+        v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0));
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     };
 
     // repeat (2)
     resetLists();
 
-    m_builder->addConstValue(2);
-    m_builder->beginRepeatLoop();
+    CompilerValue *v, *v1, *v2;
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createListAppend(globalList1.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get(), v);
 
-        m_builder->addConstValue(0);
-        m_builder->addConstValue(8.5);
-        m_builder->createListInsert(globalList2.get());
+        v1 = m_builder->addConstValue(0);
+        v2 = m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get(), v1, v2);
 
-        m_builder->addConstValue(1);
-        m_builder->addConstValue(-4.25);
-        m_builder->createListReplace(localList.get());
+        v1 = m_builder->addConstValue(1);
+        v2 = m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get(), v1, v2);
     }
     m_builder->endLoop();
 
@@ -3686,19 +3189,19 @@ TEST_F(LLVMCodeBuilderTest, LoopLists)
     // repeat(0)
     resetLists();
 
-    m_builder->addConstValue(0);
-    m_builder->beginRepeatLoop();
+    v = m_builder->addConstValue(0);
+    m_builder->beginRepeatLoop(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createListAppend(globalList1.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get(), v);
 
-        m_builder->addConstValue(0);
-        m_builder->addConstValue(8.5);
-        m_builder->createListInsert(globalList2.get());
+        v1 = m_builder->addConstValue(0);
+        v2 = m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get(), v1, v2);
 
-        m_builder->addConstValue(1);
-        m_builder->addConstValue(-4.25);
-        m_builder->createListReplace(localList.get());
+        v1 = m_builder->addConstValue(1);
+        v2 = m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get(), v1, v2);
     }
     m_builder->endLoop();
 
@@ -3707,52 +3210,52 @@ TEST_F(LLVMCodeBuilderTest, LoopLists)
     // while (counter1 < 5) { ... until (counter2 == 3) {... counter2++ }; while (false) { ... }; counter1++ }
     resetLists();
 
-    m_builder->addConstValue(0);
-    m_builder->createVariableWrite(counter1.get());
+    v = m_builder->addConstValue(0);
+    m_builder->createVariableWrite(counter1.get(), v);
 
     m_builder->beginLoopCondition();
-    m_builder->addVariableValue(counter1.get());
-    m_builder->addConstValue(5);
-    m_builder->createCmpLT();
-    m_builder->beginWhileLoop();
+    v1 = m_builder->addVariableValue(counter1.get());
+    v2 = m_builder->addConstValue(5);
+    v = m_builder->createCmpLT(v1, v2);
+    m_builder->beginWhileLoop(v);
     {
-        m_builder->addConstValue(0);
-        m_builder->createVariableWrite(counter2.get());
+        v = m_builder->addConstValue(0);
+        m_builder->createVariableWrite(counter2.get(), v);
 
         m_builder->beginLoopCondition();
-        m_builder->addVariableValue(counter2.get());
-        m_builder->addConstValue(3);
-        m_builder->createCmpEQ();
-        m_builder->beginRepeatUntilLoop();
+        v1 = m_builder->addVariableValue(counter2.get());
+        v2 = m_builder->addConstValue(3);
+        v = m_builder->createCmpEQ(v1, v2);
+        m_builder->beginRepeatUntilLoop(v);
         {
-            m_builder->addConstValue(0);
-            m_builder->addConstValue(8.5);
-            m_builder->createListInsert(globalList2.get());
-
-            m_builder->addConstValue(1);
-            m_builder->addConstValue(-4.25);
-            m_builder->createListReplace(localList.get());
-
-            m_builder->addVariableValue(counter2.get());
-            m_builder->addConstValue(1);
-            m_builder->createAdd();
-            m_builder->createVariableWrite(counter2.get());
+            v1 = m_builder->addConstValue(0);
+            v2 = m_builder->addConstValue(8.5);
+            m_builder->createListInsert(globalList2.get(), v1, v2);
+
+            v1 = m_builder->addConstValue(1);
+            v2 = m_builder->addConstValue(-4.25);
+            m_builder->createListReplace(localList.get(), v1, v2);
+
+            v1 = m_builder->addVariableValue(counter2.get());
+            v2 = m_builder->addConstValue(1);
+            v = m_builder->createAdd(v1, v2);
+            m_builder->createVariableWrite(counter2.get(), v);
         }
         m_builder->endLoop();
 
         m_builder->beginLoopCondition();
-        m_builder->addConstValue(false);
-        m_builder->beginWhileLoop();
+        v = m_builder->addConstValue(false);
+        m_builder->beginWhileLoop(v);
         {
-            m_builder->addConstValue("hello world");
-            m_builder->createListAppend(globalList1.get());
+            v = m_builder->addConstValue("hello world");
+            m_builder->createListAppend(globalList1.get(), v);
         }
         m_builder->endLoop();
 
-        m_builder->addVariableValue(counter1.get());
-        m_builder->addConstValue(1);
-        m_builder->createAdd();
-        m_builder->createVariableWrite(counter1.get());
+        v1 = m_builder->addVariableValue(counter1.get());
+        v2 = m_builder->addConstValue(1);
+        v = m_builder->createAdd(v1, v2);
+        m_builder->createVariableWrite(counter1.get(), v);
     }
     m_builder->endLoop();
 
@@ -3762,19 +3265,19 @@ TEST_F(LLVMCodeBuilderTest, LoopLists)
     resetLists();
 
     m_builder->beginLoopCondition();
-    m_builder->addConstValue(true);
-    m_builder->beginRepeatUntilLoop();
+    v = m_builder->addConstValue(true);
+    m_builder->beginRepeatUntilLoop(v);
     {
-        m_builder->addConstValue("hello world");
-        m_builder->createListAppend(globalList1.get());
+        v = m_builder->addConstValue("hello world");
+        m_builder->createListAppend(globalList1.get(), v);
 
-        m_builder->addConstValue(0);
-        m_builder->addConstValue(8.5);
-        m_builder->createListInsert(globalList2.get());
+        v1 = m_builder->addConstValue(0);
+        v2 = m_builder->addConstValue(8.5);
+        m_builder->createListInsert(globalList2.get(), v1, v2);
 
-        m_builder->addConstValue(1);
-        m_builder->addConstValue(-4.25);
-        m_builder->createListReplace(localList.get());
+        v1 = m_builder->addConstValue(1);
+        v2 = m_builder->addConstValue(-4.25);
+        m_builder->createListReplace(localList.get(), v1, v2);
     }
     m_builder->endLoop();
 
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 4aa0daeb..fff7c33d 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -9,60 +9,60 @@ class CodeBuilderMock : public ICodeBuilder
 {
     public:
         MOCK_METHOD(std::shared_ptr<ExecutableCode>, finalize, (), (override));
-        MOCK_METHOD(void, addFunctionCall, (const std::string &, Compiler::StaticType, const std::vector<Compiler::StaticType> &), (override));
-        MOCK_METHOD(void, addConstValue, (const Value &), (override));
-        MOCK_METHOD(void, addVariableValue, (Variable *), (override));
-        MOCK_METHOD(void, addListContents, (List *), (override));
-        MOCK_METHOD(void, addListItem, (List *), (override));
-        MOCK_METHOD(void, addListItemIndex, (List *), (override));
-        MOCK_METHOD(void, addListContains, (List *), (override));
-        MOCK_METHOD(void, addListSize, (List *), (override));
+        MOCK_METHOD(CompilerValue *, addFunctionCall, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override));
+        MOCK_METHOD(CompilerConstant *, addConstValue, (const Value &), (override));
+        MOCK_METHOD(CompilerValue *, addVariableValue, (Variable *), (override));
+        MOCK_METHOD(CompilerValue *, addListContents, (List *), (override));
+        MOCK_METHOD(CompilerValue *, addListItem, (List *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, addListItemIndex, (List *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, addListContains, (List *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, addListSize, (List *), (override));
 
-        MOCK_METHOD(void, createAdd, (), (override));
-        MOCK_METHOD(void, createSub, (), (override));
-        MOCK_METHOD(void, createMul, (), (override));
-        MOCK_METHOD(void, createDiv, (), (override));
+        MOCK_METHOD(CompilerValue *, createAdd, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createSub, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createMul, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createDiv, (CompilerValue *, CompilerValue *), (override));
 
-        MOCK_METHOD(void, createCmpEQ, (), (override));
-        MOCK_METHOD(void, createCmpGT, (), (override));
-        MOCK_METHOD(void, createCmpLT, (), (override));
+        MOCK_METHOD(CompilerValue *, createCmpEQ, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createCmpGT, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createCmpLT, (CompilerValue *, CompilerValue *), (override));
 
-        MOCK_METHOD(void, createAnd, (), (override));
-        MOCK_METHOD(void, createOr, (), (override));
-        MOCK_METHOD(void, createNot, (), (override));
+        MOCK_METHOD(CompilerValue *, createAnd, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createOr, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createNot, (CompilerValue *), (override));
 
-        MOCK_METHOD(void, createMod, (), (override));
-        MOCK_METHOD(void, createRound, (), (override));
-        MOCK_METHOD(void, createAbs, (), (override));
-        MOCK_METHOD(void, createFloor, (), (override));
-        MOCK_METHOD(void, createCeil, (), (override));
-        MOCK_METHOD(void, createSqrt, (), (override));
-        MOCK_METHOD(void, createSin, (), (override));
-        MOCK_METHOD(void, createCos, (), (override));
-        MOCK_METHOD(void, createTan, (), (override));
-        MOCK_METHOD(void, createAsin, (), (override));
-        MOCK_METHOD(void, createAcos, (), (override));
-        MOCK_METHOD(void, createAtan, (), (override));
-        MOCK_METHOD(void, createLn, (), (override));
-        MOCK_METHOD(void, createLog10, (), (override));
-        MOCK_METHOD(void, createExp, (), (override));
-        MOCK_METHOD(void, createExp10, (), (override));
+        MOCK_METHOD(CompilerValue *, createMod, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createRound, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createAbs, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createFloor, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createCeil, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createSqrt, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createSin, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createCos, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createTan, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createAsin, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createAcos, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createAtan, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createLn, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createLog10, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createExp, (CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createExp10, (CompilerValue *), (override));
 
-        MOCK_METHOD(void, createVariableWrite, (Variable *), (override));
+        MOCK_METHOD(void, createVariableWrite, (Variable *, CompilerValue *), (override));
 
         MOCK_METHOD(void, createListClear, (List *), (override));
-        MOCK_METHOD(void, createListRemove, (List *), (override));
-        MOCK_METHOD(void, createListAppend, (List *), (override));
-        MOCK_METHOD(void, createListInsert, (List *), (override));
-        MOCK_METHOD(void, createListReplace, (List *), (override));
+        MOCK_METHOD(void, createListRemove, (List *, CompilerValue *), (override));
+        MOCK_METHOD(void, createListAppend, (List *, CompilerValue *), (override));
+        MOCK_METHOD(void, createListInsert, (List *, CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(void, createListReplace, (List *, CompilerValue *, CompilerValue *), (override));
 
-        MOCK_METHOD(void, beginIfStatement, (), (override));
+        MOCK_METHOD(void, beginIfStatement, (CompilerValue *), (override));
         MOCK_METHOD(void, beginElseBranch, (), (override));
         MOCK_METHOD(void, endIf, (), (override));
 
-        MOCK_METHOD(void, beginRepeatLoop, (), (override));
-        MOCK_METHOD(void, beginWhileLoop, (), (override));
-        MOCK_METHOD(void, beginRepeatUntilLoop, (), (override));
+        MOCK_METHOD(void, beginRepeatLoop, (CompilerValue *), (override));
+        MOCK_METHOD(void, beginWhileLoop, (CompilerValue *), (override));
+        MOCK_METHOD(void, beginRepeatUntilLoop, (CompilerValue *), (override));
         MOCK_METHOD(void, beginLoopCondition, (), (override));
         MOCK_METHOD(void, endLoop, (), (override));
 
diff --git a/test/scratch_classes/block_test.cpp b/test/scratch_classes/block_test.cpp
index 6317287f..ab7358b7 100644
--- a/test/scratch_classes/block_test.cpp
+++ b/test/scratch_classes/block_test.cpp
@@ -17,7 +17,11 @@ using namespace libscratchcpp;
 class BlockTestMock
 {
     public:
+#ifdef USE_LLVM
+        MOCK_METHOD(CompilerValue *, compileTest, (Compiler *), ());
+#else
         MOCK_METHOD(void, compileTest, (Compiler *), ());
+#endif
 };
 
 class BlockTest : public testing::Test
@@ -25,11 +29,19 @@ class BlockTest : public testing::Test
     public:
         void SetUp() override { m_mockPtr = &m_mock; }
 
+#ifdef USE_LLVM
+        static CompilerValue *compileTest(Compiler *compiler)
+        {
+            EXPECT_TRUE(m_mockPtr);
+            return m_mockPtr->compileTest(compiler);
+        }
+#else
         static void compileTest(Compiler *compiler)
         {
             ASSERT_TRUE(m_mockPtr);
             m_mockPtr->compileTest(compiler);
         }
+#endif // USE_LLVM
 
         static inline BlockTestMock *m_mockPtr = nullptr;
         BlockTestMock m_mock;

From 0e05e3756551c9725320768c1d0cb184dd0ae45a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Nov 2024 10:01:42 +0100
Subject: [PATCH 084/259] LLVMCodeBuilder: Implement select instruction

---
 src/dev/engine/internal/icodebuilder.h        |  2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 17 +++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 73 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  2 +
 6 files changed, 97 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 4e966667..8e47ea40 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -58,6 +58,8 @@ class ICodeBuilder
         virtual CompilerValue *createExp(CompilerValue *num) = 0;
         virtual CompilerValue *createExp10(CompilerValue *num) = 0;
 
+        virtual CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) = 0;
+
         virtual void createVariableWrite(Variable *variable, CompilerValue *value) = 0;
 
         virtual void createListClear(List *list) = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 864dc252..71d0bc66 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -446,6 +446,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::Select: {
+                assert(step.args.size() == 3);
+                const auto &arg1 = step.args[0];
+                const auto &arg2 = step.args[1];
+                const auto &arg3 = step.args[2];
+                llvm::Value *cond = castValue(arg1.second, arg1.first);
+                llvm::Value *trueValue = castValue(arg2.second, arg2.first);
+                llvm::Value *falseValue = castValue(arg3.second, arg3.first);
+                step.functionReturnReg->value = m_builder.CreateSelect(cond, trueValue, falseValue);
+                break;
+            }
+
             case LLVMInstruction::Type::WriteVariable: {
                 assert(step.args.size() == 1);
                 assert(m_variablePtrs.find(step.workVariable) != m_variablePtrs.cend());
@@ -1125,6 +1137,11 @@ CompilerValue *LLVMCodeBuilder::createExp10(CompilerValue *num)
     return createOp(LLVMInstruction::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, { num });
 }
 
+CompilerValue *LLVMCodeBuilder::createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType)
+{
+    return createOp(LLVMInstruction::Type::Select, valueType, { Compiler::StaticType::Bool, valueType, valueType }, { cond, trueValue, falseValue });
+}
+
 void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value)
 {
     LLVMInstruction ins(LLVMInstruction::Type::WriteVariable);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index cd84d1a8..a7f7403d 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -65,6 +65,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         CompilerValue *createExp(CompilerValue *num) override;
         CompilerValue *createExp10(CompilerValue *num) override;
 
+        CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) override;
+
         void createVariableWrite(Variable *variable, CompilerValue *value) override;
 
         void createListClear(List *list) override;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index d1b1dcc8..d02f4d9f 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -40,6 +40,7 @@ struct LLVMInstruction
             Log10,
             Exp,
             Exp10,
+            Select,
             WriteVariable,
             ReadVariable,
             ClearList,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 07746d3d..ede70179 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1381,6 +1381,79 @@ TEST_F(LLVMCodeBuilderTest, WriteVariable)
     ASSERT_EQ(localVar3->value(), true);
 }
 
+TEST_F(LLVMCodeBuilderTest, Select)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    createBuilder(&sprite, true);
+
+    // Number
+    CompilerValue *v = m_builder->addConstValue(true);
+    v = m_builder->createSelect(v, m_builder->addConstValue(5.8), m_builder->addConstValue(-17.42), Compiler::StaticType::Number);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+    v = m_builder->addConstValue(false);
+    v = m_builder->createSelect(v, m_builder->addConstValue(5.8), m_builder->addConstValue(-17.42), Compiler::StaticType::Number);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+    // Bool
+    v = m_builder->addConstValue(true);
+    v = m_builder->createSelect(v, m_builder->addConstValue(true), m_builder->addConstValue(false), Compiler::StaticType::Bool);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+    v = m_builder->addConstValue(false);
+    v = m_builder->createSelect(v, m_builder->addConstValue(true), m_builder->addConstValue(false), Compiler::StaticType::Bool);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+    // String
+    v = m_builder->addConstValue(true);
+    v = m_builder->createSelect(v, m_builder->addConstValue("hello"), m_builder->addConstValue("world"), Compiler::StaticType::String);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    v = m_builder->addConstValue(false);
+    v = m_builder->createSelect(v, m_builder->addConstValue("hello"), m_builder->addConstValue("world"), Compiler::StaticType::String);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    // Different types
+    v = m_builder->addConstValue(true);
+    v = m_builder->createSelect(v, m_builder->addConstValue("543"), m_builder->addConstValue("true"), Compiler::StaticType::Number);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+    v = m_builder->addConstValue(false);
+    v = m_builder->createSelect(v, m_builder->addConstValue("543"), m_builder->addConstValue("true"), Compiler::StaticType::Number);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+    v = m_builder->addConstValue(true);
+    v = m_builder->createSelect(v, m_builder->addConstValue(1), m_builder->addConstValue("false"), Compiler::StaticType::Bool);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+    v = m_builder->addConstValue(false);
+    v = m_builder->createSelect(v, m_builder->addConstValue(1), m_builder->addConstValue("false"), Compiler::StaticType::Bool);
+    m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+    static const std::string expected =
+        "5.8\n"
+        "-17.42\n"
+        "1\n"
+        "0\n"
+        "hello\n"
+        "world\n"
+        "543\n"
+        "0\n"
+        "1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    testing::internal::CaptureStdout();
+    auto ctx = code->createExecutionContext(&sprite);
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, ReadVariable)
 {
     EngineMock engine;
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index fff7c33d..3097812c 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -48,6 +48,8 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(CompilerValue *, createExp, (CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createExp10, (CompilerValue *), (override));
 
+        MOCK_METHOD(CompilerValue *, createSelect, (CompilerValue *, CompilerValue *, CompilerValue *, Compiler::StaticType), (override));
+
         MOCK_METHOD(void, createVariableWrite, (Variable *, CompilerValue *), (override));
 
         MOCK_METHOD(void, createListClear, (List *), (override));

From eec4126ac256be5f5f432dc76375dd2858816157 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Nov 2024 10:08:23 +0100
Subject: [PATCH 085/259] Compiler: Add createSelect() method

---
 include/scratchcpp/dev/compiler.h   |  2 ++
 src/dev/engine/compiler.cpp         |  6 ++++++
 test/dev/compiler/compiler_test.cpp | 23 +++++++++++++++++++++++
 3 files changed, 31 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index ffc4ed7f..178d3b9a 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -87,6 +87,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *createExp(CompilerValue *num);
         CompilerValue *createExp10(CompilerValue *num);
 
+        CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType);
+
         void createVariableWrite(Variable *variable, CompilerValue *value);
 
         void createListClear(List *list);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 51583120..2b41a71c 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -288,6 +288,12 @@ CompilerValue *Compiler::createExp10(CompilerValue *num)
     return impl->builder->createExp10(num);
 }
 
+/*! Creates a select instruction (ternary operator). */
+CompilerValue *Compiler::createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, StaticType valueType)
+{
+    return impl->builder->createSelect(cond, trueValue, falseValue, valueType);
+}
+
 /*! Creates a variable write operation. */
 void Compiler::createVariableWrite(Variable *variable, CompilerValue *value)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 8eb81046..62b52baa 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -777,6 +777,29 @@ TEST_F(CompilerTest, CreateExp10)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CreateSelect)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue arg3(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+
+        EXPECT_CALL(*m_builder, createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Number)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Number), &ret);
+
+        EXPECT_CALL(*m_builder, createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Unknown)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Unknown), nullptr);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, CreateVariableWrite)
 {
     Compiler compiler(&m_engine, &m_target);

From a1decd9b377e63ceb735dd9074ff6e3901ecf5d4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Nov 2024 10:32:19 +0100
Subject: [PATCH 086/259] Add list_to_string() function

---
 src/scratch/list_functions.cpp               | 10 ++++++++
 src/scratch/list_functions.h                 |  2 ++
 test/scratch_classes/list_functions_test.cpp | 27 ++++++++++++++++++++
 3 files changed, 39 insertions(+)

diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp
index a10cb669..e5b02441 100644
--- a/src/scratch/list_functions.cpp
+++ b/src/scratch/list_functions.cpp
@@ -52,4 +52,14 @@ extern "C"
     {
         return list->size();
     }
+
+    char *list_to_string(List *list)
+    {
+        std::string str;
+        list->toString(str);
+
+        char *ret = (char *)malloc((str.size() + 1) * sizeof(char));
+        strncpy(ret, str.c_str(), str.size() + 1);
+        return ret;
+    }
 }
diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h
index 05edc4c4..64398605 100644
--- a/src/scratch/list_functions.h
+++ b/src/scratch/list_functions.h
@@ -23,6 +23,8 @@ extern "C"
     size_t *list_size_ptr(List *list);
     const size_t *list_alloc_size_ptr(List *list);
     size_t list_size(List *list);
+
+    char *list_to_string(List *list);
 }
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/list_functions_test.cpp b/test/scratch_classes/list_functions_test.cpp
index 2f10e80f..7296123d 100644
--- a/test/scratch_classes/list_functions_test.cpp
+++ b/test/scratch_classes/list_functions_test.cpp
@@ -146,3 +146,30 @@ TEST(ListFunctionsTest, Size)
         ASSERT_EQ(list_size(&list), 2);
     }
 }
+
+TEST(ListFunctionsTest, ToString)
+{
+    {
+        List list("", "test list");
+        list.append("Lorem");
+        list.append("ipsum");
+        list.append("dolor");
+        list.append("sit");
+        list.append("amet");
+
+        char *str = list_to_string(&list);
+        ASSERT_TRUE(strcmp(str, "Lorem ipsum dolor sit amet") == 0);
+        free(str);
+    }
+
+    {
+        List list("", "test list");
+        list.append(1);
+        list.append(2);
+        list.append(3);
+
+        char *str = list_to_string(&list);
+        ASSERT_TRUE(strcmp(str, "123") == 0);
+        free(str);
+    }
+}

From b88a14879e8e8ce2e708d2e3df96bfa378a1d2a9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Nov 2024 10:32:48 +0100
Subject: [PATCH 087/259] LLVMCodeBuilder: Implement list contents

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 20 +++++++-
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 48 +++++++++++++++++++
 4 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 71d0bc66..cfb4b29e 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -623,6 +623,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::GetListContents: {
+                assert(step.args.size() == 0);
+                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                llvm::Value *ptr = m_builder.CreateCall(resolve_list_to_string(), listPtr.ptr);
+                m_heap.push_back(ptr); // deallocate later
+                step.functionReturnReg->value = ptr;
+                break;
+            }
+
             case LLVMInstruction::Type::GetListItem: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
@@ -964,7 +973,10 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 
 CompilerValue *LLVMCodeBuilder::addListContents(List *list)
 {
-    return addConstValue(Value());
+    LLVMInstruction ins(LLVMInstruction::Type::GetListContents);
+    ins.workList = list;
+    m_listPtrs[list] = LLVMListPtr();
+    return createOp(ins, Compiler::StaticType::String);
 }
 
 CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
@@ -2261,6 +2273,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_alloc_size_ptr()
     return resolveFunction("list_alloc_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string()
+{
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("list_to_string", llvm::FunctionType::get(pointerType, { pointerType }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index a7f7403d..7933dc52 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -160,6 +160,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_data();
         llvm::FunctionCallee resolve_list_size_ptr();
         llvm::FunctionCallee resolve_list_alloc_size_ptr();
+        llvm::FunctionCallee resolve_list_to_string();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index d02f4d9f..e661cff5 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -48,6 +48,7 @@ struct LLVMInstruction
             AppendToList,
             InsertToList,
             ListReplace,
+            GetListContents,
             GetListItem,
             GetListSize,
             GetListItemIndex,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index ede70179..e9739cd1 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1801,6 +1801,54 @@ TEST_F(LLVMCodeBuilderTest, ListReplace)
     ASSERT_EQ(localList->toString(), "3 ipsum true hello world");
 }
 
+TEST_F(LLVMCodeBuilderTest, GetListContents)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    std::unordered_map<List *, std::string> strings;
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    globalList->append(1);
+    globalList->append(2);
+    globalList->append(3);
+
+    localList->append("Lorem");
+    localList->append("ipsum");
+    localList->append("dolor");
+    localList->append("sit");
+    strings[localList.get()] = localList->toString();
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addListContents(globalList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    v = m_builder->addListContents(localList.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    static const std::string expected =
+        "123\n"
+        "Lorem ipsum dolor sit\n";
+
+    auto code = m_builder->finalize();
+    auto ctx = code->createExecutionContext(&sprite);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+
+    ASSERT_EQ(globalList->toString(), "123");
+    ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
+}
+
 TEST_F(LLVMCodeBuilderTest, GetListItem)
 {
     EngineMock engine;

From 4309e900cdbd4e001d6bb1de1443ef6f4f840ba2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Nov 2024 11:53:25 +0100
Subject: [PATCH 088/259] Add execution context parameter to LLVM function

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp   | 11 ++++++-----
 .../engine/internal/llvm/llvmexecutablecode.cpp    |  2 +-
 src/dev/engine/internal/llvm/llvmexecutablecode.h  |  2 +-
 test/dev/llvm/llvmcodebuilder_test.cpp             |  2 +-
 test/dev/llvm/llvmexecutablecode_test.cpp          | 14 +++++++-------
 test/dev/llvm/testfunctions.cpp                    |  4 ++--
 test/dev/llvm/testfunctions.h                      |  3 ++-
 test/dev/llvm/testmock.h                           |  2 +-
 8 files changed, 21 insertions(+), 19 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index cfb4b29e..45c0b3d1 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -58,13 +58,14 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_builder.setFastMathFlags(fmf);
 
     // Create function
-    // void *f(Target *, ValueData **, List **)
+    // void *f(ExecutionContext *, Target *, ValueData **, List **)
     llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-    llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType }, false);
+    llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType, pointerType }, false);
     llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
-    llvm::Value *targetPtr = func->getArg(0);
-    llvm::Value *targetVariables = func->getArg(1);
-    llvm::Value *targetLists = func->getArg(2);
+    llvm::Value *executionContextPtr = func->getArg(0);
+    llvm::Value *targetPtr = func->getArg(1);
+    llvm::Value *targetVariables = func->getArg(2);
+    llvm::Value *targetLists = func->getArg(3);
 
     llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
     m_builder.SetInsertPoint(entry);
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
index eb8b8b03..e4f9d443 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
@@ -55,7 +55,7 @@ void LLVMExecutableCode::run(ExecutionContext *context)
         ctx->setFinished(done);
     } else {
         Target *target = ctx->target();
-        void *handle = m_mainFunction(target, target->variableData(), target->listData());
+        void *handle = m_mainFunction(context, target, target->variableData(), target->listData());
 
         if (!handle)
             ctx->setFinished(true);
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h
index 26e412dd..63524156 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.h
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.h
@@ -33,7 +33,7 @@ class LLVMExecutableCode : public ExecutableCode
     private:
         uint64_t lookupFunction(const std::string &name);
 
-        using MainFunctionType = void *(*)(Target *, ValueData **, List **);
+        using MainFunctionType = void *(*)(ExecutionContext *, Target *, ValueData **, List **);
         using ResumeFunctionType = bool (*)(void *);
 
         static LLVMExecutionContext *getContext(ExecutionContext *context);
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index e9739cd1..b12f4dc0 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -52,7 +52,7 @@ class LLVMCodeBuilderTest : public testing::Test
 
         void SetUp() override
         {
-            test_function(nullptr, nullptr, nullptr, nullptr); // force dependency
+            test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency
         }
 
         void createBuilder(Target *target, bool warp) { m_builder = std::make_unique<LLVMCodeBuilder>(target, "test", warp); }
diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp
index 84e86d15..4564d94e 100644
--- a/test/dev/llvm/llvmexecutablecode_test.cpp
+++ b/test/dev/llvm/llvmexecutablecode_test.cpp
@@ -20,7 +20,7 @@ class LLVMExecutableCodeTest : public testing::Test
         {
             m_module = std::make_unique<llvm::Module>("test", m_ctx);
             m_builder = std::make_unique<llvm::IRBuilder<>>(m_ctx);
-            test_function(nullptr, nullptr, nullptr, nullptr); // force dependency
+            test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency
 
             llvm::InitializeNativeTarget();
             llvm::InitializeNativeTargetAsmPrinter();
@@ -31,9 +31,9 @@ class LLVMExecutableCodeTest : public testing::Test
 
         llvm::Function *beginMainFunction()
         {
-            // void *f(Target *, ValueData **, List **)
+            // void *f(ExecutionContext *, Target *, ValueData **, List **)
             llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-            llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType }, false);
+            llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType, pointerType }, false);
             llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
 
             llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
@@ -57,12 +57,12 @@ class LLVMExecutableCodeTest : public testing::Test
         void addTestFunction(llvm::Function *mainFunc)
         {
             auto ptrType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
-            auto func = m_module->getOrInsertFunction("test_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType, ptrType, ptrType }, false));
+            auto func = m_module->getOrInsertFunction("test_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType, ptrType, ptrType, ptrType }, false));
 
             llvm::Constant *mockInt = llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_ctx), (uintptr_t)&m_mock, false);
             llvm::Constant *mockPtr = llvm::ConstantExpr::getIntToPtr(mockInt, ptrType);
 
-            m_builder->CreateCall(func, { mockPtr, mainFunc->getArg(0), mainFunc->getArg(1), mainFunc->getArg(2) });
+            m_builder->CreateCall(func, { mockPtr, mainFunc->getArg(0), mainFunc->getArg(1), mainFunc->getArg(2), mainFunc->getArg(3) });
         }
 
         void addTestPrintFunction(llvm::Value *arg1, llvm::Value *arg2)
@@ -110,7 +110,7 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
     auto ctx = code.createExecutionContext(&m_target);
     ASSERT_FALSE(code.isFinished(ctx.get()));
 
-    EXPECT_CALL(m_mock, f(&m_target, m_target.variableData(), m_target.listData()));
+    EXPECT_CALL(m_mock, f(ctx.get(), &m_target, m_target.variableData(), m_target.listData()));
     code.run(ctx.get());
     ASSERT_TRUE(code.isFinished(ctx.get()));
 
@@ -135,7 +135,7 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
     ASSERT_FALSE(code.isFinished(anotherCtx.get()));
     ASSERT_TRUE(code.isFinished(ctx.get()));
 
-    EXPECT_CALL(m_mock, f(&anotherTarget, anotherTarget.variableData(), anotherTarget.listData()));
+    EXPECT_CALL(m_mock, f(anotherCtx.get(), &anotherTarget, anotherTarget.variableData(), anotherTarget.listData()));
     code.run(anotherCtx.get());
     ASSERT_TRUE(code.isFinished(anotherCtx.get()));
     ASSERT_TRUE(code.isFinished(ctx.get()));
diff --git a/test/dev/llvm/testfunctions.cpp b/test/dev/llvm/testfunctions.cpp
index 8555ee24..df7e3879 100644
--- a/test/dev/llvm/testfunctions.cpp
+++ b/test/dev/llvm/testfunctions.cpp
@@ -11,10 +11,10 @@ static int counter = 0;
 
 extern "C"
 {
-    void test_function(TestMock *mock, Target *target, ValueData **varData, List **listData)
+    void test_function(TestMock *mock, ExecutionContext *ctx, Target *target, ValueData **varData, List **listData)
     {
         if (mock)
-            mock->f(target, varData, listData);
+            mock->f(ctx, target, varData, listData);
     }
 
     void test_print_function(ValueData *arg1, ValueData *arg2)
diff --git a/test/dev/llvm/testfunctions.h b/test/dev/llvm/testfunctions.h
index c20c8efa..dd864328 100644
--- a/test/dev/llvm/testfunctions.h
+++ b/test/dev/llvm/testfunctions.h
@@ -7,10 +7,11 @@ class TestMock;
 class Target;
 class ValueData;
 class List;
+class ExecutionContext;
 
 extern "C"
 {
-    void test_function(TestMock *mock, Target *target, ValueData **varData, List **listData);
+    void test_function(TestMock *mock, ExecutionContext *ctx, Target *target, ValueData **varData, List **listData);
     void test_print_function(ValueData *arg1, ValueData *arg2);
 
     void test_function_no_args(Target *target);
diff --git a/test/dev/llvm/testmock.h b/test/dev/llvm/testmock.h
index 176eeff2..5573f247 100644
--- a/test/dev/llvm/testmock.h
+++ b/test/dev/llvm/testmock.h
@@ -12,7 +12,7 @@ class List;
 class TestMock
 {
     public:
-        MOCK_METHOD(void, f, (Target *, ValueData **, List **));
+        MOCK_METHOD(void, f, (ExecutionContext * ctx, Target *, ValueData **, List **));
 };
 
 } // namespace libscratchcpp

From beb0f98f34379e3d9683b3645aa53f479f92c790 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Nov 2024 12:22:04 +0100
Subject: [PATCH 089/259] LLVMCodeBuilder: Add more function call parameter
 options

---
 src/dev/engine/internal/icodebuilder.h        |   2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  26 +++-
 .../engine/internal/llvm/llvmcodebuilder.h    |   2 +
 .../engine/internal/llvm/llvminstruction.h    |   2 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 129 ++++++++++--------
 test/dev/llvm/testfunctions.cpp               |  37 +++--
 test/dev/llvm/testfunctions.h                 |  29 ++--
 test/mocks/codebuildermock.h                  |   2 +
 8 files changed, 144 insertions(+), 85 deletions(-)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 8e47ea40..5132f1a9 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -20,6 +20,8 @@ class ICodeBuilder
         virtual std::shared_ptr<ExecutableCode> finalize() = 0;
 
         virtual CompilerValue *addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0;
+        virtual CompilerValue *addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0;
+        virtual CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0;
         virtual CompilerConstant *addConstValue(const Value &value) = 0;
         virtual CompilerValue *addVariableValue(Variable *variable) = 0;
         virtual CompilerValue *addListContents(List *list) = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 45c0b3d1..da31cea5 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -118,9 +118,17 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 std::vector<llvm::Type *> types;
                 std::vector<llvm::Value *> args;
 
+                // Add execution context arg
+                if (step.functionCtxArg) {
+                    types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0));
+                    args.push_back(executionContextPtr);
+                }
+
                 // Add target pointer arg
-                types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0));
-                args.push_back(targetPtr);
+                if (step.functionTargetArg) {
+                    types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0));
+                    args.push_back(targetPtr);
+                }
 
                 // Args
                 for (auto &arg : step.args) {
@@ -951,6 +959,20 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName,
     return nullptr;
 }
 
+CompilerValue *LLVMCodeBuilder::addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
+{
+    CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args);
+    m_instructions.back().functionTargetArg = true;
+    return ret;
+}
+
+CompilerValue *LLVMCodeBuilder::addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
+{
+    CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args);
+    m_instructions.back().functionCtxArg = true;
+    return ret;
+}
+
 CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value)
 {
     auto constReg = std::make_shared<LLVMConstantRegister>(TYPE_MAP[value.type()], value);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 7933dc52..1978ef30 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -27,6 +27,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         std::shared_ptr<ExecutableCode> finalize() override;
 
         CompilerValue *addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override;
+        CompilerValue *addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override;
+        CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override;
         CompilerConstant *addConstValue(const Value &value) override;
         CompilerValue *addVariableValue(Variable *variable) override;
         CompilerValue *addListContents(List *list) override;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index e661cff5..b770008e 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -73,6 +73,8 @@ struct LLVMInstruction
         std::string functionName;
         std::vector<std::pair<Compiler::StaticType, LLVMRegister *>> args; // target type, register
         LLVMRegister *functionReturnReg = nullptr;
+        bool functionTargetArg = false;   // whether to add target ptr to function parameters
+        bool functionCtxArg = false;      // whether to add execution context ptr to function parameters
         Variable *workVariable = nullptr; // for variables
         List *workList = nullptr;         // for lists
 };
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index b12f4dc0..e624fb9c 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -314,30 +314,44 @@ TEST_F(LLVMCodeBuilderTest, FunctionCalls)
 
     for (bool warp : warpList) {
         createBuilder(warp);
-        m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
 
-        CompilerValue *v = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+        m_builder->addFunctionCall("test_empty_function", Compiler::StaticType::Void, {}, {});
+
+        CompilerValue *v = m_builder->addConstValue("test");
+        m_builder->addFunctionCallWithCtx("test_ctx_function", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+        m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+
+        v = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+        m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         v = m_builder->addConstValue("1");
-        v = m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v });
+        v = m_builder->addTargetFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v });
         CompilerValue *v1 = m_builder->addConstValue("2");
         CompilerValue *v2 = m_builder->addConstValue("3");
-        m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 });
+        m_builder
+            ->addTargetFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 });
 
         v = m_builder->addConstValue("test");
         v1 = m_builder->addConstValue("4");
         v2 = m_builder->addConstValue("5");
-        v = m_builder->addFunctionCall(
+        v = m_builder->addTargetFunctionCall(
             "test_function_3_args_ret",
             Compiler::StaticType::String,
             { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String },
             { v, v1, v2 });
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+        m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         auto code = m_builder->finalize();
         auto ctx = code->createExecutionContext(&m_target);
 
-        static const std::string expected =
+        std::stringstream s;
+        s << ctx.get();
+        std::string ctxPtr = s.str();
+
+        const std::string expected =
+            "empty\n" + ctxPtr +
+            "\n"
+            "test\n"
             "no_args\n"
             "no_args_ret\n"
             "1_arg no_args_output\n"
@@ -2148,28 +2162,29 @@ TEST_F(LLVMCodeBuilderTest, ListContainsItem)
 TEST_F(LLVMCodeBuilderTest, Yield)
 {
     auto build = [this]() {
-        m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+        m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
 
-        CompilerValue *v = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+        CompilerValue *v = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+        m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         m_builder->yield();
 
         v = m_builder->addConstValue("1");
-        v = m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v });
+        v = m_builder->addTargetFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v });
         CompilerValue *v1 = m_builder->addConstValue("2");
         CompilerValue *v2 = m_builder->addConstValue(3);
-        m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 });
+        m_builder
+            ->addTargetFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 });
 
         v = m_builder->addConstValue("test");
         v1 = m_builder->addConstValue("4");
         v2 = m_builder->addConstValue("5");
-        v = m_builder->addFunctionCall(
+        v = m_builder->addTargetFunctionCall(
             "test_function_3_args_ret",
             Compiler::StaticType::String,
             { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String },
             { v, v1, v2 });
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+        m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     };
 
     // Without warp
@@ -2367,71 +2382,71 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     // Without else branch (const condition)
     CompilerValue *v = m_builder->addConstValue("true");
     m_builder->beginIfStatement(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endIf();
 
     v = m_builder->addConstValue("false");
     m_builder->beginIfStatement(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endIf();
 
     // Without else branch (condition returned by function)
-    CompilerValue *v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    CompilerValue *v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
     CompilerValue *v2 = m_builder->addConstValue("no_args_output");
     v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
     m_builder->beginIfStatement(v);
     v = m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
-    v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
     v2 = m_builder->addConstValue("");
     v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
     m_builder->beginIfStatement(v);
     v = m_builder->addConstValue(1);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
     // With else branch (const condition)
     v = m_builder->addConstValue("true");
     m_builder->beginIfStatement(v);
     v = m_builder->addConstValue(2);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
     v = m_builder->addConstValue(3);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
     v = m_builder->addConstValue("false");
     m_builder->beginIfStatement(v);
     v = m_builder->addConstValue(4);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
     v = m_builder->addConstValue(5);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
     // With else branch (condition returned by function)
-    v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
     v2 = m_builder->addConstValue("no_args_output");
     v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
     m_builder->beginIfStatement(v);
     v = m_builder->addConstValue(6);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
     v = m_builder->addConstValue(7);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
-    v1 = m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
+    v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {});
     v2 = m_builder->addConstValue("");
     v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 });
     m_builder->beginIfStatement(v);
     v = m_builder->addConstValue(8);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->beginElseBranch();
     v = m_builder->addConstValue(9);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endIf();
 
     // Nested 1
@@ -2442,19 +2457,19 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
         m_builder->beginIfStatement(v);
         {
             v = m_builder->addConstValue(0);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         {
             v = m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
             v = m_builder->addConstValue(false);
             m_builder->beginIfStatement(v);
             m_builder->beginElseBranch();
             {
                 v = m_builder->addConstValue(2);
-                m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+                m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
             }
             m_builder->endIf();
         }
@@ -2466,12 +2481,12 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
         m_builder->beginIfStatement(v);
         {
             v = m_builder->addConstValue(3);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         {
             v = m_builder->addConstValue(4);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endIf();
     }
@@ -2485,12 +2500,12 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
         m_builder->beginIfStatement(v);
         {
             v = m_builder->addConstValue(5);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         {
             v = m_builder->addConstValue(6);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endIf();
     }
@@ -2500,7 +2515,7 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
         m_builder->beginIfStatement(v);
         {
             v = m_builder->addConstValue(7);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->beginElseBranch();
         m_builder->endIf();
@@ -2769,36 +2784,36 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     // Const count
     CompilerValue *v = m_builder->addConstValue("-5");
     m_builder->beginRepeatLoop(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     v = m_builder->addConstValue(0);
     m_builder->beginRepeatLoop(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     v = m_builder->addConstValue(3);
     m_builder->beginRepeatLoop(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     v = m_builder->addConstValue("2.4");
     m_builder->beginRepeatLoop(v);
     v = m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endLoop();
 
     v = m_builder->addConstValue("2.5");
     m_builder->beginRepeatLoop(v);
     v = m_builder->addConstValue(1);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->endLoop();
 
     // Count returned by function
     v = m_builder->addConstValue(2);
     v = callConstFuncForType(ValueType::Number, v);
     m_builder->beginRepeatLoop(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     // Nested
@@ -2809,18 +2824,18 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
         m_builder->beginRepeatLoop(v);
         {
             v = m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endLoop();
 
         v = m_builder->addConstValue(2);
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+        m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         v = m_builder->addConstValue(3);
         m_builder->beginRepeatLoop(v);
         {
             v = m_builder->addConstValue(3);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         }
         m_builder->endLoop();
     }
@@ -2863,7 +2878,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
 
     v = m_builder->addConstValue(3);
     m_builder->beginRepeatLoop(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     code = m_builder->finalize();
@@ -2923,7 +2938,7 @@ TEST_F(LLVMCodeBuilderTest, WhileLoop)
     v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 });
     m_builder->beginWhileLoop(v);
     v = m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
@@ -2942,13 +2957,13 @@ TEST_F(LLVMCodeBuilderTest, WhileLoop)
         m_builder->beginWhileLoop(v);
         {
             v = m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
             m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
         }
         m_builder->endLoop();
 
         v = m_builder->addConstValue(2);
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+        m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         m_builder->beginLoopCondition();
         v = m_builder->addConstValue(false);
@@ -2982,7 +2997,7 @@ TEST_F(LLVMCodeBuilderTest, WhileLoop)
     m_builder->beginLoopCondition();
     v = m_builder->addConstValue(true);
     m_builder->beginWhileLoop(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     code = m_builder->finalize();
@@ -3026,7 +3041,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
     v = m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v });
     m_builder->beginRepeatUntilLoop(v);
     v = m_builder->addConstValue(0);
-    m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
@@ -3047,13 +3062,13 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
         m_builder->beginRepeatUntilLoop(v);
         {
             v = m_builder->addConstValue(1);
-            m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
             m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {});
         }
         m_builder->endLoop();
 
         v = m_builder->addConstValue(2);
-        m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+        m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
         m_builder->beginLoopCondition();
         v = m_builder->addConstValue(true);
@@ -3087,7 +3102,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
     m_builder->beginLoopCondition();
     v = m_builder->addConstValue(false);
     m_builder->beginRepeatUntilLoop(v);
-    m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
     code = m_builder->finalize();
diff --git a/test/dev/llvm/testfunctions.cpp b/test/dev/llvm/testfunctions.cpp
index df7e3879..0a78634e 100644
--- a/test/dev/llvm/testfunctions.cpp
+++ b/test/dev/llvm/testfunctions.cpp
@@ -25,6 +25,17 @@ extern "C"
         std::cout << s1 << " " << s2 << std::endl;
     }
 
+    void test_empty_function()
+    {
+        std::cout << "empty" << std::endl;
+    }
+
+    void test_ctx_function(ExecutionContext *ctx, const char *arg)
+    {
+        std::cout << ctx << std::endl;
+        std::cout << arg << std::endl;
+    }
+
     void test_function_no_args(Target *target)
     {
         target->isStage();
@@ -67,69 +78,69 @@ extern "C"
         return value_toCString(&v.data());
     }
 
-    bool test_equals(Target *target, const char *a, const char *b)
+    bool test_equals(const char *a, const char *b)
     {
         return strcmp(a, b) == 0;
     }
 
-    void test_unreachable(Target *target)
+    void test_unreachable()
     {
         std::cout << "error: unreachable reached" << std::endl;
         exit(1);
     }
 
-    bool test_lower_than(Target *target, double a, double b)
+    bool test_lower_than(double a, double b)
     {
         return a < b;
     }
 
-    double test_const_number(Target *target, double v)
+    double test_const_number(double v)
     {
         return v;
     }
 
-    bool test_const_bool(Target *target, bool v)
+    bool test_const_bool(bool v)
     {
         return v;
     }
 
-    char *test_const_string(Target *target, const char *v)
+    char *test_const_string(const char *v)
     {
         Value value(v);
         return value_toCString(&value.data());
     }
 
-    bool test_not(Target *target, bool arg)
+    bool test_not(bool arg)
     {
         return !arg;
     }
 
-    void test_reset_counter(Target *target)
+    void test_reset_counter()
     {
         counter = 0;
     }
 
-    void test_increment_counter(Target *target)
+    void test_increment_counter()
     {
         counter++;
     }
 
-    double test_get_counter(Target *target)
+    double test_get_counter()
     {
         return counter;
     }
 
-    void test_print_number(Target *target, double v)
+    void test_print_number(double v)
     {
         std::cout << v << std::endl;
     }
 
-    void test_print_bool(Target *target, bool v)
+    void test_print_bool(bool v)
     {
         std::cout << v << std::endl;
     }
 
-    void test_print_string(Target *target, const char *v)
+    void test_print_string(const char *v)
     {
         std::cout << v << std::endl;
     }
diff --git a/test/dev/llvm/testfunctions.h b/test/dev/llvm/testfunctions.h
index dd864328..49693f6b 100644
--- a/test/dev/llvm/testfunctions.h
+++ b/test/dev/llvm/testfunctions.h
@@ -14,6 +14,9 @@ extern "C"
     void test_function(TestMock *mock, ExecutionContext *ctx, Target *target, ValueData **varData, List **listData);
     void test_print_function(ValueData *arg1, ValueData *arg2);
 
+    void test_empty_function();
+    void test_ctx_function(ExecutionContext *ctx, const char *arg);
+
     void test_function_no_args(Target *target);
     char *test_function_no_args_ret(Target *target);
     void test_function_1_arg(Target *target, const char *arg1);
@@ -21,22 +24,22 @@ extern "C"
     void test_function_3_args(Target *target, const char *arg1, const char *arg2, const char *arg3);
     char *test_function_3_args_ret(Target *target, const char *arg1, const char *arg2, const char *arg3);
 
-    bool test_equals(Target *target, const char *a, const char *b);
-    bool test_lower_than(Target *target, double a, double b);
-    bool test_not(Target *target, bool arg);
-    double test_const_number(Target *target, double v);
-    bool test_const_bool(Target *target, bool v);
-    char *test_const_string(Target *target, const char *v);
+    bool test_equals(const char *a, const char *b);
+    bool test_lower_than(double a, double b);
+    bool test_not(bool arg);
+    double test_const_number(double v);
+    bool test_const_bool(bool v);
+    char *test_const_string(const char *v);
 
-    void test_unreachable(Target *target);
+    void test_unreachable();
 
-    void test_reset_counter(Target *target);
-    void test_increment_counter(Target *target);
-    double test_get_counter(Target *target);
+    void test_reset_counter();
+    void test_increment_counter();
+    double test_get_counter();
 
-    void test_print_number(Target *target, double v);
-    void test_print_bool(Target *target, bool v);
-    void test_print_string(Target *target, const char *v);
+    void test_print_number(double v);
+    void test_print_bool(bool v);
+    void test_print_string(const char *v);
 }
 
 } // namespace libscratchcpp
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 3097812c..cf1738ea 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -10,6 +10,8 @@ class CodeBuilderMock : public ICodeBuilder
     public:
         MOCK_METHOD(std::shared_ptr<ExecutableCode>, finalize, (), (override));
         MOCK_METHOD(CompilerValue *, addFunctionCall, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override));
+        MOCK_METHOD(CompilerValue *, addTargetFunctionCall, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override));
+        MOCK_METHOD(CompilerValue *, addFunctionCallWithCtx, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override));
         MOCK_METHOD(CompilerConstant *, addConstValue, (const Value &), (override));
         MOCK_METHOD(CompilerValue *, addVariableValue, (Variable *), (override));
         MOCK_METHOD(CompilerValue *, addListContents, (List *), (override));

From 19711234d04183817cf3e0f1d587f81d8d9b9d02 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 24 Nov 2024 12:26:11 +0100
Subject: [PATCH 090/259] Compiler: Add target and ctx function call methods

---
 include/scratchcpp/dev/compiler.h   |  2 ++
 src/dev/engine/compiler.cpp         | 22 ++++++++++++-
 test/dev/compiler/compiler_test.cpp | 50 +++++++++++++++++++++++++++++
 3 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 178d3b9a..06cd716c 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -48,6 +48,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         std::shared_ptr<ExecutableCode> compile(std::shared_ptr<Block> startBlock);
 
         CompilerValue *addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
+        CompilerValue *addTargetFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
+        CompilerValue *addFunctionCallWithCtx(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
         CompilerConstant *addConstValue(const Value &value);
         CompilerValue *addVariableValue(Variable *variable);
         CompilerValue *addListContents(List *list);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 2b41a71c..10e68e7d 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -76,7 +76,7 @@ std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBl
 
 /*!
  * Adds a call to the given function.\n
- * For example: extern "C" bool some_block(Target *target, double arg1, const char *arg2)
+ * For example: extern "C" bool some_block(double arg1, const char *arg2)
  */
 CompilerValue *Compiler::addFunctionCall(const std::string &functionName, StaticType returnType, const ArgTypes &argTypes, const Args &args)
 {
@@ -84,6 +84,26 @@ CompilerValue *Compiler::addFunctionCall(const std::string &functionName, Static
     return impl->builder->addFunctionCall(functionName, returnType, argTypes, args);
 }
 
+/*!
+ * Adds a call to the given function with a target parameter.\n
+ * For example: extern "C" bool some_block(Target *target, double arg1, const char *arg2)
+ */
+CompilerValue *Compiler::addTargetFunctionCall(const std::string &functionName, StaticType returnType, const ArgTypes &argTypes, const Args &args)
+{
+    assert(argTypes.size() == args.size());
+    return impl->builder->addTargetFunctionCall(functionName, returnType, argTypes, args);
+}
+
+/*!
+ * Adds a call to the given function with an execution context parameter.\n
+ * For example: extern "C" bool some_block(ExecutionContext *ctx, double arg1, const char *arg2)
+ */
+CompilerValue *Compiler::addFunctionCallWithCtx(const std::string &functionName, StaticType returnType, const ArgTypes &argTypes, const Args &args)
+{
+    assert(argTypes.size() == args.size());
+    return impl->builder->addFunctionCallWithCtx(functionName, returnType, argTypes, args);
+}
+
 /*! Adds the given constant to the compiled code. */
 CompilerConstant *Compiler::addConstValue(const Value &value)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 62b52baa..8e87191b 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -89,6 +89,56 @@ TEST_F(CompilerTest, AddFunctionCall)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, AddTargetFunctionCall)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    m_compareBlock = block;
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_EQ(compiler->block(), m_compareBlock);
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        Compiler::ArgTypes argTypes = { Compiler::StaticType::Number, Compiler::StaticType::Bool };
+        Compiler::Args args = { &arg1, &arg2 };
+        EXPECT_CALL(*m_builder, addTargetFunctionCall("test1", Compiler::StaticType::Void, argTypes, args));
+        compiler->addTargetFunctionCall("test1", Compiler::StaticType::Void, argTypes, args);
+
+        args = { &arg1 };
+        argTypes = { Compiler::StaticType::String };
+        EXPECT_CALL(*m_builder, addTargetFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args));
+        compiler->addTargetFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, AddFunctionCallWithCtx)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    m_compareBlock = block;
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_EQ(compiler->block(), m_compareBlock);
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        Compiler::ArgTypes argTypes = { Compiler::StaticType::Number, Compiler::StaticType::Bool };
+        Compiler::Args args = { &arg1, &arg2 };
+        EXPECT_CALL(*m_builder, addFunctionCallWithCtx("test1", Compiler::StaticType::Void, argTypes, args));
+        compiler->addFunctionCallWithCtx("test1", Compiler::StaticType::Void, argTypes, args);
+
+        args = { &arg1 };
+        argTypes = { Compiler::StaticType::String };
+        EXPECT_CALL(*m_builder, addFunctionCallWithCtx("test2", Compiler::StaticType::Bool, argTypes, args));
+        compiler->addFunctionCallWithCtx("test2", Compiler::StaticType::Bool, argTypes, args);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, AddConstValue)
 {
     Compiler compiler(&m_engine, &m_target);

From 6ea564431c529af64d5223dbd0acd29a420f3d8e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 25 Nov 2024 20:24:06 +0100
Subject: [PATCH 091/259] Compiler: Add API for custom while/until loops

---
 include/scratchcpp/dev/compiler.h   |  6 ++-
 src/dev/engine/compiler.cpp         | 50 ++++++++++++++++++++---
 src/dev/engine/compiler_p.h         |  1 +
 test/dev/compiler/compiler_test.cpp | 62 +++++++++++++++++++++++------
 4 files changed, 99 insertions(+), 20 deletions(-)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 06cd716c..1c1d441e 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -103,12 +103,16 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void beginElseBranch();
         void endIf();
 
+        void beginWhileLoop(CompilerValue *cond);
+        void beginRepeatUntilLoop(CompilerValue *cond);
+        void beginLoopCondition();
+        void endLoop();
+
         void moveToIf(CompilerValue *cond, std::shared_ptr<Block> substack);
         void moveToIfElse(CompilerValue *cond, std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2);
         void moveToRepeatLoop(CompilerValue *count, std::shared_ptr<Block> substack);
         void moveToWhileLoop(CompilerValue *cond, std::shared_ptr<Block> substack);
         void moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block> substack);
-        void beginLoopCondition();
         void warp();
 
         Input *input(const std::string &name) const;
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 10e68e7d..db4c0b54 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -54,6 +54,11 @@ std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBl
                 std::cerr << "error: if statement created by block '" << impl->block->opcode() << "' not terminated" << std::endl;
                 assert(false);
             }
+
+            if (impl->customLoopCount > 0) {
+                std::cerr << "error: loop created by block '" << impl->block->opcode() << "' not terminated" << std::endl;
+                assert(false);
+            }
         } else {
             std::cout << "warning: unsupported block: " << impl->block->opcode() << std::endl;
             impl->unsupportedBlocks.insert(impl->block->opcode());
@@ -382,6 +387,45 @@ void Compiler::endIf()
     impl->customIfStatementCount--;
 }
 
+/*!
+ * Begins a custom while loop.
+ * \note The loop must be terminated with endLoop() after compiling your block.
+ */
+void Compiler::beginWhileLoop(CompilerValue *cond)
+{
+    impl->builder->beginWhileLoop(cond);
+    impl->customLoopCount++;
+}
+
+/*!
+ * Begins a custom repeat until loop.
+ * \note The loop must be terminated with endLoop() after compiling your block.
+ */
+void Compiler::beginRepeatUntilLoop(CompilerValue *cond)
+{
+    impl->builder->beginRepeatUntilLoop(cond);
+    impl->customLoopCount++;
+}
+
+/*! Begins a while/until loop condition. */
+void Compiler::beginLoopCondition()
+{
+    impl->builder->beginLoopCondition();
+}
+
+/*! Ends custom loop. */
+void Compiler::endLoop()
+{
+    if (impl->customLoopCount == 0) {
+        std::cerr << "error: called Compiler::endLoop() without a loop";
+        assert(false);
+        return;
+    }
+
+    impl->builder->endLoop();
+    impl->customLoopCount--;
+}
+
 /*! Jumps to the given if substack. */
 void Compiler::moveToIf(CompilerValue *cond, std::shared_ptr<Block> substack)
 {
@@ -445,12 +489,6 @@ void Compiler::moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block>
         impl->substackEnd();
 }
 
-/*! Begins a while/until loop condition. */
-void Compiler::beginLoopCondition()
-{
-    impl->builder->beginLoopCondition();
-}
-
 /*! Makes current script run without screen refresh. */
 void Compiler::warp()
 {
diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h
index 080afc96..740d3be2 100644
--- a/src/dev/engine/compiler_p.h
+++ b/src/dev/engine/compiler_p.h
@@ -33,6 +33,7 @@ struct CompilerPrivate
 
         std::shared_ptr<Block> block;
         int customIfStatementCount = 0;
+        int customLoopCount = 0;
         std::vector<std::pair<std::pair<std::shared_ptr<Block>, std::shared_ptr<Block>>, SubstackType>> substackTree;
         bool substackHit = false;
         bool warp = false;
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 8e87191b..7e7ed85f 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -892,6 +892,55 @@ TEST_F(CompilerTest, CustomIfStatement)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CustomWhileLoop)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginWhileLoop(&arg));
+        compiler->beginWhileLoop(&arg);
+        EXPECT_CALL(*m_builder, endLoop());
+        compiler->endLoop();
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CustomRepeatUntilLoop)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg));
+        compiler->beginRepeatUntilLoop(&arg);
+        EXPECT_CALL(*m_builder, endLoop());
+        compiler->endLoop();
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, BeginLoopCondition)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_CALL(*m_builder, beginLoopCondition());
+        compiler->beginLoopCondition();
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, MoveToIf)
 {
     Compiler compiler(&m_engine, &m_target);
@@ -1369,19 +1418,6 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop)
     compile(compiler, l1);
 }
 
-TEST_F(CompilerTest, BeginLoopCondition)
-{
-    Compiler compiler(&m_engine, &m_target);
-    auto block = std::make_shared<Block>("a", "");
-    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
-        EXPECT_CALL(*m_builder, beginLoopCondition());
-        compiler->beginLoopCondition();
-        return nullptr;
-    });
-
-    compile(compiler, block);
-}
-
 TEST_F(CompilerTest, Input)
 {
     Compiler compiler(&m_engine, &m_target);

From 826935f0cff1bb066685a7c45fd32fe560359898 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 7 Dec 2024 18:25:44 +0100
Subject: [PATCH 092/259] Add Promise class

---
 CMakeLists.txt                    |  1 +
 include/scratchcpp/dev/promise.h  | 27 +++++++++++++++++++++++++++
 src/dev/engine/CMakeLists.txt     |  3 +++
 src/dev/engine/promise.cpp        | 25 +++++++++++++++++++++++++
 src/dev/engine/promise_p.cpp      |  5 +++++
 src/dev/engine/promise_p.h        | 13 +++++++++++++
 test/dev/CMakeLists.txt           |  1 +
 test/dev/promise/CMakeLists.txt   | 12 ++++++++++++
 test/dev/promise/promise_test.cpp | 14 ++++++++++++++
 9 files changed, 101 insertions(+)
 create mode 100644 include/scratchcpp/dev/promise.h
 create mode 100644 src/dev/engine/promise.cpp
 create mode 100644 src/dev/engine/promise_p.cpp
 create mode 100644 src/dev/engine/promise_p.h
 create mode 100644 test/dev/promise/CMakeLists.txt
 create mode 100644 test/dev/promise/promise_test.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 138f5540..b7723d88 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -76,6 +76,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
         include/scratchcpp/dev/compilerconstant.h
         include/scratchcpp/dev/executablecode.h
         include/scratchcpp/dev/executioncontext.h
+        include/scratchcpp/dev/promise.h
     )
 
     if(LIBSCRATCHCPP_PRINT_LLVM_IR)
diff --git a/include/scratchcpp/dev/promise.h b/include/scratchcpp/dev/promise.h
new file mode 100644
index 00000000..2c0b6010
--- /dev/null
+++ b/include/scratchcpp/dev/promise.h
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "../global.h"
+#include "../spimpl.h"
+
+namespace libscratchcpp
+{
+
+class PromisePrivate;
+
+/*! \brief The Promise class represents the eventual completion of an asynchronous operation. */
+class LIBSCRATCHCPP_EXPORT Promise
+{
+    public:
+        Promise();
+        Promise(const Promise &) = delete;
+
+        bool isResolved() const;
+        void resolve();
+
+    private:
+        spimpl::unique_impl_ptr<PromisePrivate> impl;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index a4933a98..a3e2790e 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -12,6 +12,9 @@ target_sources(scratchcpp
     executioncontext.cpp
     executioncontext_p.cpp
     executioncontext_p.h
+    promise.cpp
+    promise_p.cpp
+    promise_p.h
     internal/icodebuilder.h
     internal/icodebuilderfactory.h
     internal/codebuilderfactory.cpp
diff --git a/src/dev/engine/promise.cpp b/src/dev/engine/promise.cpp
new file mode 100644
index 00000000..4f6ed6ae
--- /dev/null
+++ b/src/dev/engine/promise.cpp
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/dev/promise.h>
+
+#include "promise_p.h"
+
+using namespace libscratchcpp;
+
+/*! Constructs Promise. */
+Promise::Promise() :
+    impl(spimpl::make_unique_impl<PromisePrivate>())
+{
+}
+
+/*! Returns true if the promise is resolved. */
+bool Promise::isResolved() const
+{
+    return impl->isResolved;
+}
+
+/*! Marks the promise as resolved. */
+void Promise::resolve()
+{
+    impl->isResolved = true;
+}
diff --git a/src/dev/engine/promise_p.cpp b/src/dev/engine/promise_p.cpp
new file mode 100644
index 00000000..bf4f7f0a
--- /dev/null
+++ b/src/dev/engine/promise_p.cpp
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "promise_p.h"
+
+using namespace libscratchcpp;
diff --git a/src/dev/engine/promise_p.h b/src/dev/engine/promise_p.h
new file mode 100644
index 00000000..1c956537
--- /dev/null
+++ b/src/dev/engine/promise_p.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace libscratchcpp
+{
+
+struct PromisePrivate
+{
+        bool isResolved = false;
+};
+
+} // namespace libscratchcpp
diff --git a/test/dev/CMakeLists.txt b/test/dev/CMakeLists.txt
index 79ee0e67..60bec96f 100644
--- a/test/dev/CMakeLists.txt
+++ b/test/dev/CMakeLists.txt
@@ -2,3 +2,4 @@ add_subdirectory(blocks)
 add_subdirectory(executioncontext)
 add_subdirectory(llvm)
 add_subdirectory(compiler)
+add_subdirectory(promise)
diff --git a/test/dev/promise/CMakeLists.txt b/test/dev/promise/CMakeLists.txt
new file mode 100644
index 00000000..71f94d71
--- /dev/null
+++ b/test/dev/promise/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(
+  promise_test
+  promise_test.cpp
+)
+
+target_link_libraries(
+  promise_test
+  GTest::gtest_main
+  scratchcpp
+)
+
+gtest_discover_tests(promise_test)
diff --git a/test/dev/promise/promise_test.cpp b/test/dev/promise/promise_test.cpp
new file mode 100644
index 00000000..d2c28c6b
--- /dev/null
+++ b/test/dev/promise/promise_test.cpp
@@ -0,0 +1,14 @@
+#include <scratchcpp/dev/promise.h>
+
+#include "../../common.h"
+
+using namespace libscratchcpp;
+
+TEST(PromiseTest, Resolve)
+{
+    Promise promise;
+    ASSERT_FALSE(promise.isResolved());
+
+    promise.resolve();
+    ASSERT_TRUE(promise.isResolved());
+}

From a415da4504759c00f7db7621d824da4cf2358652 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 7 Dec 2024 18:50:46 +0100
Subject: [PATCH 093/259] ExecutionContext: Add promise API

---
 include/scratchcpp/dev/executioncontext.h         |  4 ++++
 src/dev/engine/executioncontext.cpp               | 12 ++++++++++++
 src/dev/engine/executioncontext_p.h               |  4 ++++
 .../executioncontext/executioncontext_test.cpp    | 15 +++++++++++++++
 4 files changed, 35 insertions(+)

diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h
index 7d7ac877..4651d079 100644
--- a/include/scratchcpp/dev/executioncontext.h
+++ b/include/scratchcpp/dev/executioncontext.h
@@ -9,6 +9,7 @@ namespace libscratchcpp
 {
 
 class Target;
+class Promise;
 class ExecutionContextPrivate;
 
 /*! \brief The ExecutionContext represents the execution context of a target (can be a clone) with variables, lists, etc. */
@@ -21,6 +22,9 @@ class LIBSCRATCHCPP_EXPORT ExecutionContext
 
         Target *target() const;
 
+        std::shared_ptr<Promise> promise() const;
+        void setPromise(std::shared_ptr<Promise> promise);
+
     private:
         spimpl::unique_impl_ptr<ExecutionContextPrivate> impl;
 };
diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp
index c45156c6..81f8cb06 100644
--- a/src/dev/engine/executioncontext.cpp
+++ b/src/dev/engine/executioncontext.cpp
@@ -17,3 +17,15 @@ Target *ExecutionContext::target() const
 {
     return impl->target;
 }
+
+/*! Returns the script promise. */
+std::shared_ptr<Promise> ExecutionContext::promise() const
+{
+    return impl->promise;
+}
+
+/*! Sets the script promise (yields until the promise is resolved). */
+void ExecutionContext::setPromise(std::shared_ptr<Promise> promise)
+{
+    impl->promise = promise;
+}
diff --git a/src/dev/engine/executioncontext_p.h b/src/dev/engine/executioncontext_p.h
index fd7fad51..19a22e96 100644
--- a/src/dev/engine/executioncontext_p.h
+++ b/src/dev/engine/executioncontext_p.h
@@ -2,16 +2,20 @@
 
 #pragma once
 
+#include <memory>
+
 namespace libscratchcpp
 {
 
 class Target;
+class Promise;
 
 struct ExecutionContextPrivate
 {
         ExecutionContextPrivate(Target *target);
 
         Target *target = nullptr;
+        std::shared_ptr<Promise> promise;
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/executioncontext/executioncontext_test.cpp b/test/dev/executioncontext/executioncontext_test.cpp
index 3429ffef..d1145373 100644
--- a/test/dev/executioncontext/executioncontext_test.cpp
+++ b/test/dev/executioncontext/executioncontext_test.cpp
@@ -1,5 +1,6 @@
 #include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/target.h>
+#include <scratchcpp/dev/promise.h>
 
 #include "../../common.h"
 
@@ -11,3 +12,17 @@ TEST(ExecutionContextTest, Constructor)
     ExecutionContext ctx(&target);
     ASSERT_EQ(ctx.target(), &target);
 }
+
+TEST(ExecutionContextTest, Promise)
+{
+    Target target;
+    ExecutionContext ctx(&target);
+    ASSERT_EQ(ctx.promise(), nullptr);
+
+    auto promise = std::make_shared<Promise>();
+    ctx.setPromise(promise);
+    ASSERT_EQ(ctx.promise(), promise);
+
+    ctx.setPromise(nullptr);
+    ASSERT_EQ(ctx.promise(), nullptr);
+}

From 4dfbc525bf6627644dffeeaf966d2361bdd00c49 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 7 Dec 2024 18:51:52 +0100
Subject: [PATCH 094/259] Update promise API in Thread class

---
 include/scratchcpp/thread.h    |  8 ++++++++
 src/engine/internal/engine.cpp | 21 +++++++++++++++++++--
 src/engine/thread.cpp          | 23 +++++++++++++++--------
 test/thread/thread_test.cpp    | 17 ++++++++++++++++-
 4 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/include/scratchcpp/thread.h b/include/scratchcpp/thread.h
index f3f7693e..b5936db7 100644
--- a/include/scratchcpp/thread.h
+++ b/include/scratchcpp/thread.h
@@ -10,6 +10,9 @@ namespace libscratchcpp
 
 class VirtualMachine;
 class Target;
+#ifdef USE_LLVM
+class Promise;
+#endif
 class IEngine;
 class Script;
 class ThreadPrivate;
@@ -32,8 +35,13 @@ class LIBSCRATCHCPP_EXPORT Thread
 
         bool isFinished() const;
 
+#ifdef USE_LLVM
+        std::shared_ptr<Promise> promise() const;
+        void setPromise(std::shared_ptr<Promise> promise);
+#else
         void promise();
         void resolvePromise();
+#endif
 
     private:
         spimpl::unique_impl_ptr<ThreadPrivate> impl;
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 590bbe17..748797ce 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -9,6 +9,7 @@
 #include <scratchcpp/broadcast.h>
 #ifdef USE_LLVM
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/promise.h>
 #else
 #include <scratchcpp/compiler.h>
 #endif
@@ -593,8 +594,16 @@ void Engine::step()
         } else {
             Thread *th = senderThread;
 
-            if (std::find_if(m_threads.begin(), m_threads.end(), [th](std::shared_ptr<Thread> thread) { return thread.get() == th; }) != m_threads.end())
+            if (std::find_if(m_threads.begin(), m_threads.end(), [th](std::shared_ptr<Thread> thread) { return thread.get() == th; }) != m_threads.end()) {
+#ifdef USE_LLVM
+                auto promise = th->promise();
+
+                if (promise)
+                    promise->resolve();
+#else
                 th->resolvePromise();
+#endif
+            }
 
             resolved.push_back(broadcast);
             resolvedThreads.push_back(th);
@@ -2018,8 +2027,16 @@ void Engine::addBroadcastPromise(Broadcast *broadcast, Thread *sender, bool wait
     // Resolve broadcast promise if it's already running
     auto it = m_broadcastSenders.find(broadcast);
 
-    if (it != m_broadcastSenders.cend() && std::find_if(m_threads.begin(), m_threads.end(), [&it](std::shared_ptr<Thread> thread) { return thread.get() == it->second; }) != m_threads.end())
+    if (it != m_broadcastSenders.cend() && std::find_if(m_threads.begin(), m_threads.end(), [&it](std::shared_ptr<Thread> thread) { return thread.get() == it->second; }) != m_threads.end()) {
+#ifdef USE_LLVM
+        auto promise = it->second->promise();
+
+        if (promise)
+            promise->resolve();
+#else
         it->second->resolvePromise();
+#endif
+    }
 
     if (wait)
         m_broadcastSenders[broadcast] = sender;
diff --git a/src/engine/thread.cpp b/src/engine/thread.cpp
index 2ffe2e56..3c1cbfc5 100644
--- a/src/engine/thread.cpp
+++ b/src/engine/thread.cpp
@@ -5,6 +5,7 @@
 #ifdef USE_LLVM
 #include <scratchcpp/script.h>
 #include <scratchcpp/dev/executablecode.h>
+#include <scratchcpp/dev/executioncontext.h>
 #endif
 
 #include "thread_p.h"
@@ -87,22 +88,28 @@ bool Thread::isFinished() const
 #endif
 }
 
+#ifdef USE_LLVM
+/*! Returns the script promise. */
+std::shared_ptr<Promise> Thread::promise() const
+{
+    return impl->executionContext->promise();
+}
+
+/*! Sets the script promise (yields until the promise is resolved). */
+void Thread::setPromise(std::shared_ptr<Promise> promise)
+{
+    impl->executionContext->setPromise(promise);
+}
+#else
 /*! Pauses the script (when it's executed using run() again) until resolvePromise() is called. */
 void Thread::promise()
 {
-#ifdef USE_LLVM
-    impl->code->promise();
-#else
     impl->vm->promise();
-#endif
 }
 
 /*! Resolves the promise and resumes the script. */
 void Thread::resolvePromise()
 {
-#ifdef USE_LLVM
-    impl->code->resolvePromise();
-#else
     impl->vm->resolvePromise();
-#endif
 }
+#endif // USE_LLVM
diff --git a/test/thread/thread_test.cpp b/test/thread/thread_test.cpp
index 08f1c943..65114c0d 100644
--- a/test/thread/thread_test.cpp
+++ b/test/thread/thread_test.cpp
@@ -2,6 +2,7 @@
 #include <scratchcpp/script.h>
 #include <scratchcpp/virtualmachine.h>
 #include <scratchcpp/dev/executioncontext.h>
+#include <scratchcpp/dev/promise.h>
 #include <targetmock.h>
 #include <enginemock.h>
 #include <executablecodemock.h>
@@ -76,5 +77,19 @@ TEST_F(ThreadTest, IsFinished)
     ASSERT_TRUE(m_thread->isFinished());
 }
 
-// TODO: Test promise() and resolvePromise()
+TEST_F(ThreadTest, Promise)
+{
+    ASSERT_EQ(m_thread->promise(), m_ctx->promise());
+
+    auto promise = std::make_shared<Promise>();
+    m_ctx->setPromise(promise);
+    ASSERT_EQ(m_thread->promise(), m_ctx->promise());
+
+    m_ctx->setPromise(nullptr);
+    ASSERT_EQ(m_thread->promise(), m_ctx->promise());
+
+    m_thread->setPromise(promise);
+    ASSERT_EQ(m_thread->promise(), promise);
+    ASSERT_EQ(m_ctx->promise(), promise);
+}
 #endif // USE_LLVM

From e0143a6206f3aabb4203a189fc64c35bf7180a8d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 7 Dec 2024 18:52:51 +0100
Subject: [PATCH 095/259] Remove old promise API from ExecutableCode

---
 include/scratchcpp/dev/executablecode.h             | 6 ------
 src/dev/engine/internal/llvm/llvmexecutablecode.cpp | 8 --------
 src/dev/engine/internal/llvm/llvmexecutablecode.h   | 3 ---
 test/mocks/executablecodemock.h                     | 3 ---
 4 files changed, 20 deletions(-)

diff --git a/include/scratchcpp/dev/executablecode.h b/include/scratchcpp/dev/executablecode.h
index 54efea83..ba5d03e4 100644
--- a/include/scratchcpp/dev/executablecode.h
+++ b/include/scratchcpp/dev/executablecode.h
@@ -30,12 +30,6 @@ class LIBSCRATCHCPP_EXPORT ExecutableCode
         /*! Returns true if the code is stopped or finished. */
         virtual bool isFinished(ExecutionContext *context) const = 0;
 
-        /*! Pauses the script (when it's executed using run() again) until resolvePromise() is called. */
-        virtual void promise() = 0;
-
-        /*! Resolves the promise and resumes the script. */
-        virtual void resolvePromise() = 0;
-
         /*! Creates an execution context for the given Target. */
         virtual std::shared_ptr<ExecutionContext> createExecutionContext(Target *target) const = 0;
 };
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
index e4f9d443..6d1b0a8b 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
@@ -83,14 +83,6 @@ bool LLVMExecutableCode::isFinished(ExecutionContext *context) const
     return getContext(context)->finished();
 }
 
-void LLVMExecutableCode::promise()
-{
-}
-
-void LLVMExecutableCode::resolvePromise()
-{
-}
-
 std::shared_ptr<ExecutionContext> LLVMExecutableCode::createExecutionContext(Target *target) const
 {
     return std::make_shared<LLVMExecutionContext>(target);
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h
index 63524156..ecdc09c3 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.h
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.h
@@ -25,9 +25,6 @@ class LLVMExecutableCode : public ExecutableCode
 
         bool isFinished(ExecutionContext *context) const override;
 
-        void promise() override;
-        void resolvePromise() override;
-
         std::shared_ptr<ExecutionContext> createExecutionContext(Target *target) const override;
 
     private:
diff --git a/test/mocks/executablecodemock.h b/test/mocks/executablecodemock.h
index f01b9f97..0958c391 100644
--- a/test/mocks/executablecodemock.h
+++ b/test/mocks/executablecodemock.h
@@ -14,8 +14,5 @@ class ExecutableCodeMock : public ExecutableCode
 
         MOCK_METHOD(bool, isFinished, (ExecutionContext *), (const, override));
 
-        MOCK_METHOD(void, promise, (), (override));
-        MOCK_METHOD(void, resolvePromise, (), (override));
-
         MOCK_METHOD(std::shared_ptr<ExecutionContext>, createExecutionContext, (Target *), (const, override));
 };

From 4477e02bcd595f7c16269e8e332b7e4ca8f93b03 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 7 Dec 2024 19:47:35 +0100
Subject: [PATCH 096/259] LLVMExecutableCode: Implement promises

---
 .../internal/llvm/llvmexecutablecode.cpp      | 12 +++
 test/dev/llvm/llvmexecutablecode_test.cpp     | 92 +++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
index 6d1b0a8b..944e5d44 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
@@ -4,6 +4,7 @@
 #include <scratchcpp/value_functions.h>
 #include <scratchcpp/stage.h>
 #include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/promise.h>
 #include <llvm/Support/Error.h>
 #include <iostream>
 
@@ -46,6 +47,15 @@ void LLVMExecutableCode::run(ExecutionContext *context)
     if (ctx->finished())
         return;
 
+    auto promise = ctx->promise();
+
+    if (promise) {
+        if (promise->isResolved())
+            ctx->setPromise(nullptr);
+        else
+            return;
+    }
+
     if (ctx->coroutineHandle()) {
         bool done = m_resumeFunction(ctx->coroutineHandle());
 
@@ -69,6 +79,7 @@ void LLVMExecutableCode::kill(ExecutionContext *context)
     LLVMExecutionContext *ctx = getContext(context);
     ctx->setCoroutineHandle(nullptr);
     ctx->setFinished(true);
+    ctx->setPromise(nullptr);
 }
 
 void LLVMExecutableCode::reset(ExecutionContext *context)
@@ -76,6 +87,7 @@ void LLVMExecutableCode::reset(ExecutionContext *context)
     LLVMExecutionContext *ctx = getContext(context);
     ctx->setCoroutineHandle(nullptr);
     ctx->setFinished(false);
+    ctx->setPromise(nullptr);
 }
 
 bool LLVMExecutableCode::isFinished(ExecutionContext *context) const
diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp
index 4564d94e..def0f985 100644
--- a/test/dev/llvm/llvmexecutablecode_test.cpp
+++ b/test/dev/llvm/llvmexecutablecode_test.cpp
@@ -2,6 +2,7 @@
 #include <scratchcpp/value_functions.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
+#include <scratchcpp/dev/promise.h>
 #include <dev/engine/internal/llvm/llvmexecutablecode.h>
 #include <dev/engine/internal/llvm/llvmexecutioncontext.h>
 #include <llvm/Support/TargetSelect.h>
@@ -144,3 +145,94 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
     ASSERT_TRUE(code.isFinished(anotherCtx.get()));
     ASSERT_FALSE(code.isFinished(ctx.get()));
 }
+
+TEST_F(LLVMExecutableCodeTest, Promise)
+{
+    auto f = beginMainFunction();
+    addTestFunction(f);
+    endFunction(nullPointer());
+
+    beginResumeFunction();
+    endFunction(m_builder->getInt1(true));
+
+    LLVMExecutableCode code(std::move(m_module));
+    auto ctx = code.createExecutionContext(&m_target);
+    ASSERT_FALSE(code.isFinished(ctx.get()));
+
+    // run()
+    auto promise = std::make_shared<Promise>();
+    ctx->setPromise(promise);
+    EXPECT_CALL(m_mock, f).Times(0);
+
+    for (int i = 0; i < 10; i++) {
+        code.run(ctx.get());
+        ASSERT_FALSE(code.isFinished(ctx.get()));
+    }
+
+    promise->resolve();
+
+    EXPECT_CALL(m_mock, f);
+    code.run(ctx.get());
+    ASSERT_TRUE(code.isFinished(ctx.get()));
+    ASSERT_EQ(ctx->promise(), nullptr);
+    code.reset(ctx.get());
+
+    // kill()
+    promise = std::make_shared<Promise>();
+    ctx->setPromise(promise);
+    EXPECT_CALL(m_mock, f).Times(0);
+
+    for (int i = 0; i < 10; i++) {
+        code.run(ctx.get());
+        ASSERT_FALSE(code.isFinished(ctx.get()));
+    }
+
+    code.kill(ctx.get());
+    ASSERT_TRUE(code.isFinished(ctx.get()));
+    ASSERT_EQ(ctx->promise(), nullptr);
+    code.reset(ctx.get());
+
+    // reset()
+    promise = std::make_shared<Promise>();
+    ctx->setPromise(promise);
+    EXPECT_CALL(m_mock, f).Times(0);
+
+    for (int i = 0; i < 10; i++) {
+        code.run(ctx.get());
+        ASSERT_FALSE(code.isFinished(ctx.get()));
+    }
+
+    code.reset(ctx.get());
+    ASSERT_FALSE(code.isFinished(ctx.get()));
+    ASSERT_EQ(ctx->promise(), nullptr);
+
+    EXPECT_CALL(m_mock, f);
+    code.run(ctx.get());
+    ASSERT_TRUE(code.isFinished(ctx.get()));
+
+    // Test with another context
+    Target anotherTarget;
+    auto anotherCtx = code.createExecutionContext(&anotherTarget);
+    ASSERT_FALSE(code.isFinished(anotherCtx.get()));
+    ASSERT_TRUE(code.isFinished(ctx.get()));
+
+    promise = std::make_shared<Promise>();
+    anotherCtx->setPromise(promise);
+    EXPECT_CALL(m_mock, f).Times(0);
+
+    for (int i = 0; i < 10; i++) {
+        code.run(anotherCtx.get());
+        ASSERT_FALSE(code.isFinished(anotherCtx.get()));
+    }
+
+    promise->resolve();
+
+    EXPECT_CALL(m_mock, f);
+    code.run(anotherCtx.get());
+    ASSERT_TRUE(code.isFinished(anotherCtx.get()));
+    ASSERT_TRUE(code.isFinished(ctx.get()));
+
+    code.reset(ctx.get());
+    ASSERT_TRUE(code.isFinished(anotherCtx.get()));
+    ASSERT_FALSE(code.isFinished(ctx.get()));
+}

From aa4e1ea69cd71d62644ab58bddaac02f4025308d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 8 Dec 2024 10:49:33 +0100
Subject: [PATCH 097/259] RandomGenerator: Fix except = start and except = end
 edge cases

---
 src/engine/internal/randomgenerator.cpp       | 17 ++++++++---------
 test/randomgenerator/randomgenerator_test.cpp | 12 ++++++++++++
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/src/engine/internal/randomgenerator.cpp b/src/engine/internal/randomgenerator.cpp
index e9e7c572..9f441238 100644
--- a/src/engine/internal/randomgenerator.cpp
+++ b/src/engine/internal/randomgenerator.cpp
@@ -57,13 +57,12 @@ long RandomGenerator::randintExcept(long start, long end, long except) const
             return end;
         else
             return start;
-    }
-
-    if (randint(0, 1) == 0) {
-        std::uniform_int_distribution<long> distribution(start, except - 1);
-        return distribution(*m_generator);
-    } else {
-        std::uniform_int_distribution<long> distribution(except + 1, end);
-        return distribution(*m_generator);
-    }
+    } else if (except == start)
+        return randint(except + 1, end);
+    else if (except == end)
+        return randint(start, except - 1);
+    else if (randint(0, 1) == 0)
+        return randint(start, except - 1);
+    else
+        return randint(except + 1, end);
 }
diff --git a/test/randomgenerator/randomgenerator_test.cpp b/test/randomgenerator/randomgenerator_test.cpp
index 06b9dda0..12032e95 100644
--- a/test/randomgenerator/randomgenerator_test.cpp
+++ b/test/randomgenerator/randomgenerator_test.cpp
@@ -96,4 +96,16 @@ TEST(RandomGeneratorTest, RandIntExcept)
         num = rng->randintExcept(1, 2, 2);
         ASSERT_EQ(num, 1);
     }
+
+    for (int i = 0; i < 25; i++) {
+        num = rng->randintExcept(1, 5, 1);
+        ASSERT_GE(num, 2);
+        ASSERT_LE(num, 5);
+    }
+
+    for (int i = 0; i < 25; i++) {
+        num = rng->randintExcept(1, 5, 5);
+        ASSERT_GE(num, 1);
+        ASSERT_LE(num, 4);
+    }
 }

From c748bad1fb0e435c32fffb2b0447c957446166c7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 8 Dec 2024 12:10:15 +0100
Subject: [PATCH 098/259] fix #601: Do not treat booleans as numbers in
 List::toString()

---
 include/scratchcpp/list.h          |  2 +-
 test/scratch_classes/list_test.cpp | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h
index ad85b3a3..d11d98e8 100644
--- a/include/scratchcpp/list.h
+++ b/include/scratchcpp/list.h
@@ -157,7 +157,7 @@ class LIBSCRATCHCPP_EXPORT List : public Entity
                 strings.push_back(std::string());
                 value_toString(item, &strings.back());
 
-                if (value_isValidNumber(item) && !strings.back().empty()) {
+                if (value_isValidNumber(item) && !value_isBool(item) && !strings.back().empty()) {
                     double doubleNum = value_toDouble(item);
                     long num = value_toLong(item);
 
diff --git a/test/scratch_classes/list_test.cpp b/test/scratch_classes/list_test.cpp
index 587996e8..0fef8bb6 100644
--- a/test/scratch_classes/list_test.cpp
+++ b/test/scratch_classes/list_test.cpp
@@ -337,6 +337,20 @@ TEST(ListTest, ToString)
     list.toString(s);
     ASSERT_EQ(s, "098");
     ASSERT_EQ(list.toString(), "098");
+
+    list.clear();
+    list.append("true");
+    list.append("false");
+    list.toString(s);
+    ASSERT_EQ(s, "true false");
+    ASSERT_EQ(list.toString(), "true false");
+
+    list.clear();
+    list.append(true);
+    list.append(false);
+    list.toString(s);
+    ASSERT_EQ(s, "true false");
+    ASSERT_EQ(list.toString(), "true false");
 }
 
 TEST(ListTest, Clone)

From 273a6c2382716b7c5109311243e690d14f76af89 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 9 Dec 2024 20:41:32 +0100
Subject: [PATCH 099/259] Add ScriptBuilder class

---
 CMakeLists.txt                              |   1 +
 include/scratchcpp/dev/test/scriptbuilder.h |  58 ++++++
 src/dev/CMakeLists.txt                      |   1 +
 src/dev/test/CMakeLists.txt                 |   6 +
 src/dev/test/scriptbuilder.cpp              | 198 ++++++++++++++++++++
 src/dev/test/scriptbuilder_p.cpp            |  12 ++
 src/dev/test/scriptbuilder_p.h              |  35 ++++
 test/dev/CMakeLists.txt                     |   1 +
 test/dev/test_api/CMakeLists.txt            |  25 +++
 test/dev/test_api/scriptbuilder_test.cpp    | 131 +++++++++++++
 test/dev/test_api/testextension.cpp         |  80 ++++++++
 test/dev/test_api/testextension.h           |  25 +++
 12 files changed, 573 insertions(+)
 create mode 100644 include/scratchcpp/dev/test/scriptbuilder.h
 create mode 100644 src/dev/test/CMakeLists.txt
 create mode 100644 src/dev/test/scriptbuilder.cpp
 create mode 100644 src/dev/test/scriptbuilder_p.cpp
 create mode 100644 src/dev/test/scriptbuilder_p.h
 create mode 100644 test/dev/test_api/CMakeLists.txt
 create mode 100644 test/dev/test_api/scriptbuilder_test.cpp
 create mode 100644 test/dev/test_api/testextension.cpp
 create mode 100644 test/dev/test_api/testextension.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b7723d88..27fc54d0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,6 +77,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
         include/scratchcpp/dev/executablecode.h
         include/scratchcpp/dev/executioncontext.h
         include/scratchcpp/dev/promise.h
+        include/scratchcpp/dev/test/scriptbuilder.h
     )
 
     if(LIBSCRATCHCPP_PRINT_LLVM_IR)
diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h
new file mode 100644
index 00000000..532f5a67
--- /dev/null
+++ b/include/scratchcpp/dev/test/scriptbuilder.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <vector>
+
+#include "../../global.h"
+#include "../../spimpl.h"
+
+namespace libscratchcpp
+{
+
+class IExtension;
+class IEngine;
+class Target;
+class List;
+
+} // namespace libscratchcpp
+
+namespace libscratchcpp::test
+{
+
+class ScriptBuilderPrivate;
+
+/*! \brief The ScriptBuilder class is used to build Scratch scripts in unit tests. */
+class LIBSCRATCHCPP_EXPORT ScriptBuilder
+{
+    public:
+        ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target);
+        ScriptBuilder(const ScriptBuilder &) = delete;
+
+        ~ScriptBuilder();
+
+        void addBlock(const std::string &opcode);
+        void addReporterBlock(const std::string &opcode);
+        void captureBlockReturnValue();
+
+        void addValueInput(const std::string &name, const Value &value);
+        void addNullInput(const std::string &name);
+
+        void addObscuredInput(const std::string &name, std::shared_ptr<Block> valueBlock);
+        void addNullObscuredInput(const std::string &name);
+
+        void addDropdownInput(const std::string &name, const std::string &selectedValue);
+        void addDropdownField(const std::string &name, const std::string &selectedValue);
+
+        void build();
+        void run();
+
+        List *capturedValues() const;
+
+    private:
+        void addBlock(std::shared_ptr<Block> block);
+
+        spimpl::unique_impl_ptr<ScriptBuilderPrivate> impl;
+};
+
+} // namespace libscratchcpp::test
diff --git a/src/dev/CMakeLists.txt b/src/dev/CMakeLists.txt
index 696178cd..4c9b3cd6 100644
--- a/src/dev/CMakeLists.txt
+++ b/src/dev/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_subdirectory(blocks)
 add_subdirectory(engine)
+add_subdirectory(test)
diff --git a/src/dev/test/CMakeLists.txt b/src/dev/test/CMakeLists.txt
new file mode 100644
index 00000000..d447cb2a
--- /dev/null
+++ b/src/dev/test/CMakeLists.txt
@@ -0,0 +1,6 @@
+target_sources(scratchcpp
+  PRIVATE
+    scriptbuilder.cpp
+    scriptbuilder_p.cpp
+    scriptbuilder_p.h
+)
diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
new file mode 100644
index 00000000..8cd1e037
--- /dev/null
+++ b/src/dev/test/scriptbuilder.cpp
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/dev/test/scriptbuilder.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/dev/executablecode.h>
+#include <scratchcpp/stage.h>
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/list.h>
+
+#include "scriptbuilder_p.h"
+
+using namespace libscratchcpp;
+using namespace libscratchcpp::test;
+
+static std::unordered_map<IEngine *, std::shared_ptr<List>> captureLists;
+
+/*! Constructs ScriptBuilder. */
+ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target) :
+    impl(spimpl::make_unique_impl<ScriptBuilderPrivate>(engine, target))
+{
+    // Create capture list
+    if (captureLists.find(engine) != captureLists.cend()) {
+        std::cerr << "error: only one ScriptBuilder can be created for each engine" << std::endl;
+        return;
+    }
+
+    captureLists[engine] = std::make_shared<List>("", "");
+
+    // Add start hat block
+    auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "script_builder_init");
+    engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
+        compiler->engine()->addGreenFlagScript(compiler->block());
+        return nullptr;
+    });
+    addBlock(block);
+
+    // Add compile function for return value capture block
+    engine->addCompileFunction(extension, "script_builder_capture", [](Compiler *compiler) -> CompilerValue * {
+        CompilerValue *input = compiler->addInput("VALUE");
+        compiler->createListAppend(captureLists[compiler->engine()].get(), input);
+        return nullptr;
+    });
+}
+
+/*! Destroys ScriptBuilder. */
+ScriptBuilder::~ScriptBuilder()
+{
+    captureLists.erase(impl->engine);
+}
+
+/*! Adds a block with the given opcode to the script. */
+void ScriptBuilder::addBlock(const std::string &opcode)
+{
+    impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), opcode);
+    addBlock(impl->lastBlock);
+}
+
+/*! Creates a reporter block with the given opcode to be used with captureBlockReturnValue() later. */
+void ScriptBuilder::addReporterBlock(const std::string &opcode)
+{
+    impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), opcode);
+}
+
+/*! Captures the return value of the created reporter block. It can be retrieved using capturedValues() later. */
+void ScriptBuilder::captureBlockReturnValue()
+{
+    if (!impl->lastBlock)
+        return;
+
+    auto valueBlock = impl->lastBlock;
+    addBlock("script_builder_capture");
+    addObscuredInput("VALUE", valueBlock);
+}
+
+/*! Adds a simple input with a value to the current block. */
+void ScriptBuilder::addValueInput(const std::string &name, const Value &value)
+{
+    if (!impl->lastBlock)
+        return;
+
+    auto input = std::make_shared<Input>(name, Input::Type::Shadow);
+    input->setPrimaryValue(value);
+    impl->lastBlock->addInput(input);
+}
+
+/*! Adds a null input (zero) to the current block. */
+void ScriptBuilder::addNullInput(const std::string &name)
+{
+    if (!impl->lastBlock)
+        return;
+
+    auto input = std::make_shared<Input>(name, Input::Type::Shadow);
+    impl->lastBlock->addInput(input);
+}
+
+/*! Adds an input obscured by the given block to the current block. */
+void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Block> valueBlock)
+{
+    if (!impl->lastBlock)
+        return;
+
+    valueBlock->setId(std::to_string(impl->blockId++));
+    impl->inputBlocks.push_back(valueBlock);
+
+    auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
+    input->setValueBlock(valueBlock);
+    impl->lastBlock->addInput(input);
+}
+
+/*! Adds an input obscured by a block which returns zero to the current block. */
+void ScriptBuilder::addNullObscuredInput(const std::string &name)
+{
+    if (!impl->lastBlock)
+        return;
+
+    auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
+    auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "");
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
+    input->setValueBlock(block);
+    impl->inputBlocks.push_back(block);
+    impl->blocks.back()->addInput(input);
+}
+
+/*! Adds a dropdown menu input to the current block. */
+void ScriptBuilder::addDropdownInput(const std::string &name, const std::string &selectedValue)
+{
+    if (!impl->lastBlock)
+        return;
+
+    auto block = impl->blocks.back();
+    auto input = std::make_shared<Input>(name, Input::Type::Shadow);
+    block->addInput(input);
+
+    auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), block->opcode() + "_menu");
+    menu->setShadow(true);
+    impl->inputBlocks.push_back(menu);
+    input->setValueBlock(menu);
+
+    auto field = std::make_shared<Field>(name, selectedValue);
+    menu->addField(field);
+}
+
+/*! Adds a dropdown field to the current block. */
+void ScriptBuilder::addDropdownField(const std::string &name, const std::string &selectedValue)
+{
+    if (!impl->lastBlock)
+        return;
+
+    auto field = std::make_shared<Field>(name, selectedValue);
+    impl->blocks.back()->addField(field);
+}
+
+/*! Builds and compiles the script. */
+void ScriptBuilder::build()
+{
+    if (impl->target->blocks().empty()) {
+        for (auto block : impl->blocks)
+            impl->target->addBlock(block);
+
+        for (auto block : impl->inputBlocks)
+            impl->target->addBlock(block);
+    }
+
+    std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
+
+    if (std::find(targets.begin(), targets.end(), impl->target) == targets.end()) {
+        targets.push_back(impl->target);
+        impl->engine->setTargets({ impl->target });
+    }
+
+    impl->engine->compile();
+}
+
+/*! Runs the built script. */
+void ScriptBuilder::run()
+{
+    impl->engine->run();
+}
+
+/*! Returns the list of captured block return values. */
+List *ScriptBuilder::capturedValues() const
+{
+    return captureLists[impl->engine].get();
+}
+
+void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
+{
+    if (!impl->blocks.empty()) {
+        auto lastBlock = impl->blocks.back();
+        lastBlock->setNext(block);
+        block->setParent(lastBlock);
+    }
+
+    impl->blocks.push_back(block);
+}
diff --git a/src/dev/test/scriptbuilder_p.cpp b/src/dev/test/scriptbuilder_p.cpp
new file mode 100644
index 00000000..f2d3fa29
--- /dev/null
+++ b/src/dev/test/scriptbuilder_p.cpp
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "scriptbuilder_p.h"
+
+using namespace libscratchcpp;
+using namespace libscratchcpp::test;
+
+ScriptBuilderPrivate::ScriptBuilderPrivate(IEngine *engine, std::shared_ptr<Target> target) :
+    engine(engine),
+    target(target)
+{
+}
diff --git a/src/dev/test/scriptbuilder_p.h b/src/dev/test/scriptbuilder_p.h
new file mode 100644
index 00000000..9b332b54
--- /dev/null
+++ b/src/dev/test/scriptbuilder_p.h
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <memory>
+#include <vector>
+
+namespace libscratchcpp
+{
+
+class IEngine;
+class Target;
+class Block;
+class List;
+
+} // namespace libscratchcpp
+
+namespace libscratchcpp::test
+{
+
+class ScriptBuilderPrivate
+{
+    public:
+        ScriptBuilderPrivate(IEngine *engine, std::shared_ptr<Target> target);
+        ScriptBuilderPrivate(const ScriptBuilderPrivate &) = delete;
+
+        IEngine *engine = nullptr;
+        std::shared_ptr<Target> target;
+        std::shared_ptr<Block> lastBlock;
+        std::vector<std::shared_ptr<Block>> blocks;
+        std::vector<std::shared_ptr<Block>> inputBlocks;
+        unsigned int blockId = 0;
+};
+
+} // namespace libscratchcpp::test
diff --git a/test/dev/CMakeLists.txt b/test/dev/CMakeLists.txt
index 60bec96f..b9db18f7 100644
--- a/test/dev/CMakeLists.txt
+++ b/test/dev/CMakeLists.txt
@@ -3,3 +3,4 @@ add_subdirectory(executioncontext)
 add_subdirectory(llvm)
 add_subdirectory(compiler)
 add_subdirectory(promise)
+add_subdirectory(test_api)
diff --git a/test/dev/test_api/CMakeLists.txt b/test/dev/test_api/CMakeLists.txt
new file mode 100644
index 00000000..cd5f8141
--- /dev/null
+++ b/test/dev/test_api/CMakeLists.txt
@@ -0,0 +1,25 @@
+add_library(
+  test_api_test_deps SHARED
+  testextension.cpp
+  testextension.h
+)
+
+target_link_libraries(
+  test_api_test_deps
+  GTest::gtest_main
+  scratchcpp
+)
+
+add_executable(
+  test_api_test
+  scriptbuilder_test.cpp
+)
+
+target_link_libraries(
+  test_api_test
+  GTest::gtest_main
+  scratchcpp
+  test_api_test_deps
+)
+
+gtest_discover_tests(test_api_test)
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
new file mode 100644
index 00000000..c8ea801a
--- /dev/null
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -0,0 +1,131 @@
+#include <scratchcpp/dev/test/scriptbuilder.h>
+#include <scratchcpp/project.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/value.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/list.h>
+
+#include "../../common.h"
+#include "testextension.h"
+
+using namespace libscratchcpp;
+using namespace libscratchcpp::test;
+
+class ScriptBuilderTest : public testing::Test
+{
+    public:
+        void SetUp() override
+        {
+            m_engine = m_project.engine().get();
+            m_extension.registerBlocks(m_engine);
+            m_target = std::make_shared<Sprite>();
+            m_builder = std::make_unique<ScriptBuilder>(&m_extension, m_engine, m_target);
+        }
+
+        Project m_project;
+        IEngine *m_engine = nullptr;
+        std::shared_ptr<Target> m_target;
+        TestExtension m_extension;
+        std::unique_ptr<ScriptBuilder> m_builder;
+};
+
+TEST_F(ScriptBuilderTest, AddBlock)
+{
+    m_builder->addBlock("test_simple");
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
+}
+
+TEST_F(ScriptBuilderTest, AddValueInput)
+{
+    m_builder->addBlock("test_print");
+    m_builder->addValueInput("STRING", 10);
+    m_builder->addBlock("test_print");
+    m_builder->addValueInput("STRING", "Hello world");
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "10\nHello world\n");
+}
+
+TEST_F(ScriptBuilderTest, AddNullInput)
+{
+    m_builder->addBlock("test_print");
+    m_builder->addNullInput("STRING");
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "0\n");
+}
+
+TEST_F(ScriptBuilderTest, AddObscuredInput)
+{
+    m_builder->addBlock("test_print");
+    auto block = std::make_shared<Block>("", "test_teststr");
+    m_builder->addObscuredInput("STRING", block);
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
+}
+
+TEST_F(ScriptBuilderTest, AddNullObscuredInput)
+{
+    m_builder->addBlock("test_print");
+    m_builder->addNullObscuredInput("STRING");
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "0\n");
+}
+
+TEST_F(ScriptBuilderTest, AddDropdownInput)
+{
+    m_builder->addBlock("test_print_dropdown");
+    m_builder->addDropdownInput("STRING", "hello");
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "hello\n");
+}
+
+TEST_F(ScriptBuilderTest, AddDropdownField)
+{
+    m_builder->addBlock("test_print_field");
+    m_builder->addDropdownField("STRING", "hello");
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "hello\n");
+}
+
+TEST_F(ScriptBuilderTest, ReporterBlocks)
+{
+    m_builder->addReporterBlock("test_teststr");
+    m_builder->captureBlockReturnValue();
+
+    m_builder->addReporterBlock("test_input");
+    m_builder->addValueInput("INPUT", -93.4);
+    m_builder->captureBlockReturnValue();
+
+    m_builder->build();
+    m_builder->run();
+
+    List *values = m_builder->capturedValues();
+    ASSERT_TRUE(values);
+    ASSERT_EQ(values->size(), 2);
+    std::string str;
+    value_toString(&values->operator[](0), &str);
+    ASSERT_EQ(str, "test");
+    ASSERT_EQ(value_toDouble(&values->operator[](1)), -93.4);
+}
diff --git a/test/dev/test_api/testextension.cpp b/test/dev/test_api/testextension.cpp
new file mode 100644
index 00000000..d24cbf3a
--- /dev/null
+++ b/test/dev/test_api/testextension.cpp
@@ -0,0 +1,80 @@
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/value.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
+#include <gtest/gtest.h>
+#include <iostream>
+
+#include "testextension.h"
+
+using namespace libscratchcpp;
+
+std::string TestExtension::name() const
+{
+    return "test";
+}
+
+std::string TestExtension::description() const
+{
+    return "";
+}
+
+void TestExtension::registerBlocks(IEngine *engine)
+{
+    engine->addCompileFunction(this, "test_simple", &compileSimple);
+    engine->addCompileFunction(this, "test_print", &compilePrint);
+    engine->addCompileFunction(this, "test_print_dropdown", &compilePrintDropdown);
+    engine->addCompileFunction(this, "test_print_field", &compilePrintField);
+    engine->addCompileFunction(this, "test_teststr", &compileTestStr);
+    engine->addCompileFunction(this, "test_input", &compileInput);
+}
+
+CompilerValue *TestExtension::compileSimple(Compiler *compiler)
+{
+    compiler->addFunctionCall("test_simple", Compiler::StaticType::Void);
+    return nullptr;
+}
+
+CompilerValue *TestExtension::compilePrint(Compiler *compiler)
+{
+    CompilerValue *input = compiler->addInput("STRING");
+    compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+    return nullptr;
+}
+
+CompilerValue *TestExtension::compilePrintDropdown(Compiler *compiler)
+{
+    EXPECT_TRUE(compiler->input("STRING")->pointsToDropdownMenu());
+    CompilerValue *input = compiler->addInput("STRING");
+    compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+    return nullptr;
+}
+
+CompilerValue *TestExtension::compilePrintField(Compiler *compiler)
+{
+    CompilerValue *input = compiler->addConstValue(compiler->field("STRING")->value());
+    compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+    return nullptr;
+}
+
+CompilerValue *TestExtension::compileTestStr(Compiler *compiler)
+{
+    return compiler->addConstValue("test");
+}
+
+CompilerValue *TestExtension::compileInput(Compiler *compiler)
+{
+    return compiler->addInput("INPUT");
+}
+
+extern "C" void test_simple()
+{
+    std::cout << "test" << std::endl;
+}
+
+extern "C" void test_print(const char *string)
+{
+    std::cout << string << std::endl;
+}
diff --git a/test/dev/test_api/testextension.h b/test/dev/test_api/testextension.h
new file mode 100644
index 00000000..699d520f
--- /dev/null
+++ b/test/dev/test_api/testextension.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include <scratchcpp/iextension.h>
+
+namespace libscratchcpp
+{
+
+class TestExtension : public IExtension
+{
+    public:
+        std::string name() const override;
+        std::string description() const override;
+
+        void registerBlocks(IEngine *engine) override;
+
+    private:
+        static CompilerValue *compileSimple(Compiler *compiler);
+        static CompilerValue *compilePrint(Compiler *compiler);
+        static CompilerValue *compilePrintDropdown(Compiler *compiler);
+        static CompilerValue *compilePrintField(Compiler *compiler);
+        static CompilerValue *compileTestStr(Compiler *compiler);
+        static CompilerValue *compileInput(Compiler *compiler);
+};
+
+} // namespace libscratchcpp

From c9e4f1e9484ef9778ba1142e08ac2a57c234510f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 10:14:22 +0100
Subject: [PATCH 100/259] ScriptBuilder: Add currentBlock() method

---
 include/scratchcpp/dev/test/scriptbuilder.h |  2 +
 src/dev/test/scriptbuilder.cpp              |  6 ++
 test/dev/test_api/scriptbuilder_test.cpp    | 63 ++++++++++++++++++++-
 3 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h
index 532f5a67..36c254ce 100644
--- a/include/scratchcpp/dev/test/scriptbuilder.h
+++ b/include/scratchcpp/dev/test/scriptbuilder.h
@@ -44,6 +44,8 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
         void addDropdownInput(const std::string &name, const std::string &selectedValue);
         void addDropdownField(const std::string &name, const std::string &selectedValue);
 
+        std::shared_ptr<Block> currentBlock() const;
+
         void build();
         void run();
 
diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index 8cd1e037..b11889f0 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -153,6 +153,12 @@ void ScriptBuilder::addDropdownField(const std::string &name, const std::string
     impl->blocks.back()->addField(field);
 }
 
+/*! Returns the current block (can be used e. g. with a custom Compiler instance). */
+std::shared_ptr<Block> ScriptBuilder::currentBlock() const
+{
+    return impl->lastBlock;
+}
+
 /*! Builds and compiles the script. */
 void ScriptBuilder::build()
 {
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index c8ea801a..414467b7 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -4,6 +4,8 @@
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/value.h>
 #include <scratchcpp/block.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
 #include <scratchcpp/list.h>
 
 #include "../../common.h"
@@ -32,7 +34,13 @@ class ScriptBuilderTest : public testing::Test
 
 TEST_F(ScriptBuilderTest, AddBlock)
 {
+    ASSERT_EQ(m_builder->currentBlock(), nullptr);
+
     m_builder->addBlock("test_simple");
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_simple");
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
@@ -44,8 +52,20 @@ TEST_F(ScriptBuilderTest, AddValueInput)
 {
     m_builder->addBlock("test_print");
     m_builder->addValueInput("STRING", 10);
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "STRING");
+
     m_builder->addBlock("test_print");
     m_builder->addValueInput("STRING", "Hello world");
+    block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "STRING");
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
@@ -57,6 +77,12 @@ TEST_F(ScriptBuilderTest, AddNullInput)
 {
     m_builder->addBlock("test_print");
     m_builder->addNullInput("STRING");
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "STRING");
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
@@ -67,8 +93,14 @@ TEST_F(ScriptBuilderTest, AddNullInput)
 TEST_F(ScriptBuilderTest, AddObscuredInput)
 {
     m_builder->addBlock("test_print");
-    auto block = std::make_shared<Block>("", "test_teststr");
-    m_builder->addObscuredInput("STRING", block);
+    auto valueBlock = std::make_shared<Block>("", "test_teststr");
+    m_builder->addObscuredInput("STRING", valueBlock);
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "STRING");
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
@@ -80,6 +112,12 @@ TEST_F(ScriptBuilderTest, AddNullObscuredInput)
 {
     m_builder->addBlock("test_print");
     m_builder->addNullObscuredInput("STRING");
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "STRING");
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
@@ -91,6 +129,12 @@ TEST_F(ScriptBuilderTest, AddDropdownInput)
 {
     m_builder->addBlock("test_print_dropdown");
     m_builder->addDropdownInput("STRING", "hello");
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print_dropdown");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "STRING");
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
@@ -102,6 +146,13 @@ TEST_F(ScriptBuilderTest, AddDropdownField)
 {
     m_builder->addBlock("test_print_field");
     m_builder->addDropdownField("STRING", "hello");
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print_field");
+    ASSERT_TRUE(block->inputs().empty());
+    ASSERT_EQ(block->fields().size(), 1);
+    ASSERT_EQ(block->fieldAt(0)->name(), "STRING");
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
@@ -112,10 +163,18 @@ TEST_F(ScriptBuilderTest, AddDropdownField)
 TEST_F(ScriptBuilderTest, ReporterBlocks)
 {
     m_builder->addReporterBlock("test_teststr");
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_teststr");
     m_builder->captureBlockReturnValue();
 
     m_builder->addReporterBlock("test_input");
     m_builder->addValueInput("INPUT", -93.4);
+    block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_input");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "INPUT");
     m_builder->captureBlockReturnValue();
 
     m_builder->build();

From baa7933e0075badc3ff96e71b47a3df9d5ed32a5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 10:43:52 +0100
Subject: [PATCH 101/259] ScriptBuilder: Set compile functions of blocks in
 currentBlock()

---
 include/scratchcpp/dev/test/scriptbuilder.h |  3 +-
 src/dev/test/scriptbuilder.cpp              | 55 ++++++++++++++-------
 test/dev/test_api/scriptbuilder_test.cpp    |  1 +
 3 files changed, 39 insertions(+), 20 deletions(-)

diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h
index 36c254ce..4a0c188a 100644
--- a/include/scratchcpp/dev/test/scriptbuilder.h
+++ b/include/scratchcpp/dev/test/scriptbuilder.h
@@ -44,7 +44,7 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
         void addDropdownInput(const std::string &name, const std::string &selectedValue);
         void addDropdownField(const std::string &name, const std::string &selectedValue);
 
-        std::shared_ptr<Block> currentBlock() const;
+        std::shared_ptr<Block> currentBlock();
 
         void build();
         void run();
@@ -53,6 +53,7 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
 
     private:
         void addBlock(std::shared_ptr<Block> block);
+        void build(std::shared_ptr<Target> target);
 
         spimpl::unique_impl_ptr<ScriptBuilderPrivate> impl;
 };
diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index b11889f0..3b9f16ef 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -6,7 +6,7 @@
 #include <scratchcpp/field.h>
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/dev/executablecode.h>
-#include <scratchcpp/stage.h>
+#include <scratchcpp/sprite.h>
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/list.h>
 
@@ -153,31 +153,26 @@ void ScriptBuilder::addDropdownField(const std::string &name, const std::string
     impl->blocks.back()->addField(field);
 }
 
-/*! Returns the current block (can be used e. g. with a custom Compiler instance). */
-std::shared_ptr<Block> ScriptBuilder::currentBlock() const
+/*!
+ * Returns the current block (can be used e. g. with a custom Compiler instance).\n
+ * The script is automatically built to set the compile function of the block.
+ * \note This method is not intended for building scripts, use build() for that.
+ */
+std::shared_ptr<Block> ScriptBuilder::currentBlock()
 {
+    if (!impl->lastBlock)
+        return nullptr;
+
+    if (!impl->lastBlock->compileFunction())
+        build(std::make_shared<Sprite>());
+
     return impl->lastBlock;
 }
 
 /*! Builds and compiles the script. */
 void ScriptBuilder::build()
 {
-    if (impl->target->blocks().empty()) {
-        for (auto block : impl->blocks)
-            impl->target->addBlock(block);
-
-        for (auto block : impl->inputBlocks)
-            impl->target->addBlock(block);
-    }
-
-    std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
-
-    if (std::find(targets.begin(), targets.end(), impl->target) == targets.end()) {
-        targets.push_back(impl->target);
-        impl->engine->setTargets({ impl->target });
-    }
-
-    impl->engine->compile();
+    build(impl->target);
 }
 
 /*! Runs the built script. */
@@ -202,3 +197,25 @@ void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
 
     impl->blocks.push_back(block);
 }
+
+void ScriptBuilder::build(std::shared_ptr<Target> target)
+{
+    impl->engine->clear();
+
+    if (target->blocks().empty()) {
+        for (auto block : impl->blocks)
+            target->addBlock(block);
+
+        for (auto block : impl->inputBlocks)
+            target->addBlock(block);
+    }
+
+    std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
+
+    if (std::find(targets.begin(), targets.end(), target) == targets.end()) {
+        targets.push_back(target);
+        impl->engine->setTargets({ target });
+    }
+
+    impl->engine->compile();
+}
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index 414467b7..eaec3336 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -40,6 +40,7 @@ TEST_F(ScriptBuilderTest, AddBlock)
     auto block = m_builder->currentBlock();
     ASSERT_TRUE(block);
     ASSERT_EQ(block->opcode(), "test_simple");
+    ASSERT_TRUE(block->compileFunction());
 
     m_builder->build();
 

From 02f80acc1c47a31271d8d40cac8acec4d07b1144 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 10:44:37 +0100
Subject: [PATCH 102/259] Implement event_whentouchingobject

---
 src/dev/blocks/eventblocks.cpp        | 10 +++++++++
 src/dev/blocks/eventblocks.h          |  3 +++
 test/dev/blocks/event_blocks_test.cpp | 30 ++++++++++++++++++++++++++-
 3 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 07477984..807949b6 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -1,5 +1,8 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+
 #include "eventblocks.h"
 
 using namespace libscratchcpp;
@@ -16,4 +19,11 @@ std::string libscratchcpp::EventBlocks::description() const
 
 void EventBlocks::registerBlocks(IEngine *engine)
 {
+    engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject);
+}
+
+CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
+{
+    compiler->engine()->addWhenTouchingObjectScript(compiler->block());
+    return nullptr;
 }
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index c395c3de..8633cc46 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -14,6 +14,9 @@ class EventBlocks : public IExtension
         std::string description() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+    private:
+        static CompilerValue *compileWhenTouchingObject(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index ab8370a6..c1563d01 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -1,15 +1,43 @@
+#include <scratchcpp/project.h>
+#include <scratchcpp/dev/test/scriptbuilder.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/sprite.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "dev/blocks/eventblocks.h"
 
 using namespace libscratchcpp;
+using namespace libscratchcpp::test;
 
 class EventBlocksTest : public testing::Test
 {
     public:
-        void SetUp() override { m_extension = std::make_unique<EventBlocks>(); }
+        void SetUp() override
+        {
+            m_extension = std::make_unique<EventBlocks>();
+            m_engine = m_project.engine().get();
+            m_extension->registerBlocks(m_engine);
+        }
 
         std::unique_ptr<IExtension> m_extension;
+        Project m_project;
+        IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
 };
+
+// TODO: Add test for when touching object hat predicate
+
+TEST_F(EventBlocksTest, WhenTouchingObject)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whentouchingobject");
+    builder.addDropdownInput("TOUCHINGOBJECTMENU", "Sprite1");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addWhenTouchingObjectScript(block));
+    compiler.compile(block);
+}

From 6c288f8cac029ffdd1e0df7cc3d3f8884c07db54 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 11:11:56 +0100
Subject: [PATCH 103/259] Implement event_whenflagclicked

---
 src/dev/blocks/eventblocks.cpp        |  7 +++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 13 +++++++++++++
 3 files changed, 21 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 807949b6..2feea715 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -20,6 +20,7 @@ std::string libscratchcpp::EventBlocks::description() const
 void EventBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject);
+    engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -27,3 +28,9 @@ CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
     compiler->engine()->addWhenTouchingObjectScript(compiler->block());
     return nullptr;
 }
+
+CompilerValue *EventBlocks::compileWhenFlagClicked(Compiler *compiler)
+{
+    compiler->engine()->addGreenFlagScript(compiler->block());
+    return nullptr;
+}
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 8633cc46..4dfb36fa 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -17,6 +17,7 @@ class EventBlocks : public IExtension
 
     private:
         static CompilerValue *compileWhenTouchingObject(Compiler *compiler);
+        static CompilerValue *compileWhenFlagClicked(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index c1563d01..d64be6bc 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -41,3 +41,16 @@ TEST_F(EventBlocksTest, WhenTouchingObject)
     EXPECT_CALL(m_engineMock, addWhenTouchingObjectScript(block));
     compiler.compile(block);
 }
+
+TEST_F(EventBlocksTest, WhenFlagClicked)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whenflagclicked");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addGreenFlagScript(block));
+    compiler.compile(block);
+}

From 5ad63a495472f386130a18b3646c7925036c2f86 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 11:24:55 +0100
Subject: [PATCH 104/259] Implement event_whenthisspriteclicked

---
 src/dev/blocks/eventblocks.cpp        |  7 +++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 13 +++++++++++++
 3 files changed, 21 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 2feea715..908cd751 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -21,6 +21,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject);
     engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked);
+    engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -34,3 +35,9 @@ CompilerValue *EventBlocks::compileWhenFlagClicked(Compiler *compiler)
     compiler->engine()->addGreenFlagScript(compiler->block());
     return nullptr;
 }
+
+CompilerValue *EventBlocks::compileWhenThisSpriteClicked(Compiler *compiler)
+{
+    compiler->engine()->addTargetClickScript(compiler->block());
+    return nullptr;
+}
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 4dfb36fa..81e6191f 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -18,6 +18,7 @@ class EventBlocks : public IExtension
     private:
         static CompilerValue *compileWhenTouchingObject(Compiler *compiler);
         static CompilerValue *compileWhenFlagClicked(Compiler *compiler);
+        static CompilerValue *compileWhenThisSpriteClicked(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index d64be6bc..6280c356 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -54,3 +54,16 @@ TEST_F(EventBlocksTest, WhenFlagClicked)
     EXPECT_CALL(m_engineMock, addGreenFlagScript(block));
     compiler.compile(block);
 }
+
+TEST_F(EventBlocksTest, WhenThisSpriteClicked)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whenthisspriteclicked");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addTargetClickScript(block));
+    compiler.compile(block);
+}

From 06718d696b5711146bee494a2c42d04fafae078d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 11:47:43 +0100
Subject: [PATCH 105/259] Implement event_whenstageclicked

---
 src/dev/blocks/eventblocks.cpp        |  7 +++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 13 +++++++++++++
 3 files changed, 21 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 908cd751..0237e375 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -22,6 +22,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject);
     engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked);
     engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked);
+    engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -41,3 +42,9 @@ CompilerValue *EventBlocks::compileWhenThisSpriteClicked(Compiler *compiler)
     compiler->engine()->addTargetClickScript(compiler->block());
     return nullptr;
 }
+
+CompilerValue *EventBlocks::compileWhenStageClicked(Compiler *compiler)
+{
+    compiler->engine()->addTargetClickScript(compiler->block());
+    return nullptr;
+}
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 81e6191f..46f3a0dc 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -19,6 +19,7 @@ class EventBlocks : public IExtension
         static CompilerValue *compileWhenTouchingObject(Compiler *compiler);
         static CompilerValue *compileWhenFlagClicked(Compiler *compiler);
         static CompilerValue *compileWhenThisSpriteClicked(Compiler *compiler);
+        static CompilerValue *compileWhenStageClicked(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index 6280c356..f3459345 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -67,3 +67,16 @@ TEST_F(EventBlocksTest, WhenThisSpriteClicked)
     EXPECT_CALL(m_engineMock, addTargetClickScript(block));
     compiler.compile(block);
 }
+
+TEST_F(EventBlocksTest, WhenStageClicked)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whenstageclicked");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addTargetClickScript(block));
+    compiler.compile(block);
+}

From 6a63262090d98f7d351c4ce2f2d511afd7f4c045 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 11:53:42 +0100
Subject: [PATCH 106/259] ScriptBuilder: Use last block in all input/field
 methods

---
 src/dev/test/scriptbuilder.cpp | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index 3b9f16ef..a49751b1 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -121,7 +121,7 @@ void ScriptBuilder::addNullObscuredInput(const std::string &name)
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
     input->setValueBlock(block);
     impl->inputBlocks.push_back(block);
-    impl->blocks.back()->addInput(input);
+    impl->lastBlock->addInput(input);
 }
 
 /*! Adds a dropdown menu input to the current block. */
@@ -130,11 +130,10 @@ void ScriptBuilder::addDropdownInput(const std::string &name, const std::string
     if (!impl->lastBlock)
         return;
 
-    auto block = impl->blocks.back();
     auto input = std::make_shared<Input>(name, Input::Type::Shadow);
-    block->addInput(input);
+    impl->lastBlock->addInput(input);
 
-    auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), block->opcode() + "_menu");
+    auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), impl->lastBlock->opcode() + "_menu");
     menu->setShadow(true);
     impl->inputBlocks.push_back(menu);
     input->setValueBlock(menu);
@@ -150,7 +149,7 @@ void ScriptBuilder::addDropdownField(const std::string &name, const std::string
         return;
 
     auto field = std::make_shared<Field>(name, selectedValue);
-    impl->blocks.back()->addField(field);
+    impl->lastBlock->addField(field);
 }
 
 /*!

From 1f9dd99a1918765e534fc09abac89b7d7f8b9709 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 12:16:03 +0100
Subject: [PATCH 107/259] Engine: Add missing stage null checks

---
 src/engine/internal/engine.cpp | 16 ++++++++++------
 1 file changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 748797ce..1be628aa 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -1526,10 +1526,12 @@ std::shared_ptr<Variable> Engine::getVariable(const std::string &id, Target *tar
     int index;
 
     // Check stage
-    index = stage->findVariableById(id);
+    if (stage) {
+        index = stage->findVariableById(id);
 
-    if (index != -1)
-        return stage->variableAt(index);
+        if (index != -1)
+            return stage->variableAt(index);
+    }
 
     // Check currently compiled target
     if (target != stage) {
@@ -1562,10 +1564,12 @@ std::shared_ptr<List> Engine::getList(const std::string &id, Target *target)
     int index;
 
     // Check stage
-    index = stage->findListById(id);
+    if (stage) {
+        index = stage->findListById(id);
 
-    if (index != -1)
-        return stage->listAt(index);
+        if (index != -1)
+            return stage->listAt(index);
+    }
 
     // Check currently compiled target
     if (target != stage) {

From 258d02818f0ae09be694e48626f0784f2150be4b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 12:16:27 +0100
Subject: [PATCH 108/259] ScriptBuilder: Add addEntityField() method

---
 include/scratchcpp/dev/test/scriptbuilder.h |  3 +++
 src/dev/test/scriptbuilder.cpp              | 13 +++++++++++--
 test/dev/test_api/scriptbuilder_test.cpp    | 17 +++++++++++++++++
 3 files changed, 31 insertions(+), 2 deletions(-)

diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h
index 4a0c188a..1cf5142b 100644
--- a/include/scratchcpp/dev/test/scriptbuilder.h
+++ b/include/scratchcpp/dev/test/scriptbuilder.h
@@ -13,6 +13,7 @@ namespace libscratchcpp
 class IExtension;
 class IEngine;
 class Target;
+class Entity;
 class List;
 
 } // namespace libscratchcpp
@@ -44,6 +45,8 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
         void addDropdownInput(const std::string &name, const std::string &selectedValue);
         void addDropdownField(const std::string &name, const std::string &selectedValue);
 
+        void addEntityField(const std::string &name, std::shared_ptr<Entity> entity);
+
         std::shared_ptr<Block> currentBlock();
 
         void build();
diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index a49751b1..1bdcd9f0 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -152,6 +152,17 @@ void ScriptBuilder::addDropdownField(const std::string &name, const std::string
     impl->lastBlock->addField(field);
 }
 
+/*! Adds a field pointing to an entity (variable, list, broadcast, etc.) */
+void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptr<Entity> entity)
+{
+    if (!impl->lastBlock)
+        return;
+
+    entity->setId(std::to_string(impl->blockId++));
+    auto field = std::make_shared<Field>(name, Value(), entity);
+    impl->lastBlock->addField(field);
+}
+
 /*!
  * Returns the current block (can be used e. g. with a custom Compiler instance).\n
  * The script is automatically built to set the compile function of the block.
@@ -199,8 +210,6 @@ void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
 
 void ScriptBuilder::build(std::shared_ptr<Target> target)
 {
-    impl->engine->clear();
-
     if (target->blocks().empty()) {
         for (auto block : impl->blocks)
             target->addBlock(block);
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index eaec3336..45fafd4e 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -7,6 +7,7 @@
 #include <scratchcpp/input.h>
 #include <scratchcpp/field.h>
 #include <scratchcpp/list.h>
+#include <scratchcpp/broadcast.h>
 
 #include "../../common.h"
 #include "testextension.h"
@@ -161,6 +162,22 @@ TEST_F(ScriptBuilderTest, AddDropdownField)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), "hello\n");
 }
 
+TEST_F(ScriptBuilderTest, AddEntityField)
+{
+    auto broadcast = std::make_shared<Broadcast>("", "");
+    m_engine->setBroadcasts({ broadcast });
+
+    m_builder->addBlock("test_print_field");
+    m_builder->addEntityField("STRING", broadcast);
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_print_field");
+    ASSERT_TRUE(block->inputs().empty());
+    ASSERT_EQ(block->fields().size(), 1);
+    ASSERT_EQ(block->fieldAt(0)->name(), "STRING");
+    ASSERT_EQ(block->fieldAt(0)->valuePtr(), broadcast);
+}
+
 TEST_F(ScriptBuilderTest, ReporterBlocks)
 {
     m_builder->addReporterBlock("test_teststr");

From 1e933d8999fc2cad6fbf81e32e520b59ef8f862d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 12:26:10 +0100
Subject: [PATCH 109/259] Implement event_whenbroadcastreceived

---
 src/dev/blocks/eventblocks.cpp        | 17 +++++++++++++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 19 +++++++++++++++++++
 3 files changed, 37 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 0237e375..71566730 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -2,6 +2,9 @@
 
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/field.h>
+#include <scratchcpp/broadcast.h>
 
 #include "eventblocks.h"
 
@@ -23,6 +26,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked);
     engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked);
     engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked);
+    engine->addCompileFunction(this, "event_whenbroadcastreceived", &compileWhenBroadcastReceived);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -48,3 +52,16 @@ CompilerValue *EventBlocks::compileWhenStageClicked(Compiler *compiler)
     compiler->engine()->addTargetClickScript(compiler->block());
     return nullptr;
 }
+
+CompilerValue *EventBlocks::compileWhenBroadcastReceived(Compiler *compiler)
+{
+    auto block = compiler->block();
+    Field *field = compiler->field("BROADCAST_OPTION");
+
+    if (field) {
+        auto broadcast = std::static_pointer_cast<Broadcast>(field->valuePtr());
+        compiler->engine()->addBroadcastScript(block, field, broadcast.get());
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 46f3a0dc..113d7278 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -20,6 +20,7 @@ class EventBlocks : public IExtension
         static CompilerValue *compileWhenFlagClicked(Compiler *compiler);
         static CompilerValue *compileWhenThisSpriteClicked(Compiler *compiler);
         static CompilerValue *compileWhenStageClicked(Compiler *compiler);
+        static CompilerValue *compileWhenBroadcastReceived(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index f3459345..88c62aa8 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -2,6 +2,8 @@
 #include <scratchcpp/dev/test/scriptbuilder.h>
 #include <scratchcpp/dev/compiler.h>
 #include <scratchcpp/sprite.h>
+#include <scratchcpp/broadcast.h>
+#include <scratchcpp/block.h>
 #include <enginemock.h>
 
 #include "../common.h"
@@ -80,3 +82,20 @@ TEST_F(EventBlocksTest, WhenStageClicked)
     EXPECT_CALL(m_engineMock, addTargetClickScript(block));
     compiler.compile(block);
 }
+
+TEST_F(EventBlocksTest, WhenBroadcastReceived)
+{
+    auto broadcast = std::make_shared<Broadcast>("", "");
+    m_engine->setBroadcasts({ broadcast });
+
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whenbroadcastreceived");
+    builder.addEntityField("BROADCAST_OPTION", broadcast);
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addBroadcastScript(block, block->fieldAt(0).get(), broadcast.get()));
+    compiler.compile(block);
+}

From 715a95e2b8c4afdaa72523fa2f3890881c84a6da Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 14:21:42 +0100
Subject: [PATCH 110/259] Implement event_whenbackropswitchesto

---
 src/dev/blocks/eventblocks.cpp        | 12 ++++++++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 14 ++++++++++++++
 3 files changed, 27 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 71566730..f3ca6136 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -27,6 +27,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked);
     engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked);
     engine->addCompileFunction(this, "event_whenbroadcastreceived", &compileWhenBroadcastReceived);
+    engine->addCompileFunction(this, "event_whenbackdropswitchesto", &compileWhenBackdropSwitchesTo);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -65,3 +66,14 @@ CompilerValue *EventBlocks::compileWhenBroadcastReceived(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *EventBlocks::compileWhenBackdropSwitchesTo(Compiler *compiler)
+{
+    auto block = compiler->block();
+    Field *field = compiler->field("BACKDROP");
+
+    if (field)
+        compiler->engine()->addBackdropChangeScript(block, field);
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 113d7278..47fc0b7b 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -21,6 +21,7 @@ class EventBlocks : public IExtension
         static CompilerValue *compileWhenThisSpriteClicked(Compiler *compiler);
         static CompilerValue *compileWhenStageClicked(Compiler *compiler);
         static CompilerValue *compileWhenBroadcastReceived(Compiler *compiler);
+        static CompilerValue *compileWhenBackdropSwitchesTo(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index 88c62aa8..8d19168e 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -99,3 +99,17 @@ TEST_F(EventBlocksTest, WhenBroadcastReceived)
     EXPECT_CALL(m_engineMock, addBroadcastScript(block, block->fieldAt(0).get(), broadcast.get()));
     compiler.compile(block);
 }
+
+TEST_F(EventBlocksTest, WhenBackdropSwitchesTo)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whenbackdropswitchesto");
+    builder.addDropdownField("BACKDROP", "backdrop2");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addBackdropChangeScript(block, block->fieldAt(0).get()));
+    compiler.compile(block);
+}

From c8dd2f3590993d412f1ead199dc472c32d0f07e3 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 14:46:49 +0100
Subject: [PATCH 111/259] Implement event_whengreaterthan

---
 src/dev/blocks/eventblocks.cpp        |  7 +++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 17 +++++++++++++++++
 3 files changed, 25 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index f3ca6136..097cf9c4 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -28,6 +28,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked);
     engine->addCompileFunction(this, "event_whenbroadcastreceived", &compileWhenBroadcastReceived);
     engine->addCompileFunction(this, "event_whenbackdropswitchesto", &compileWhenBackdropSwitchesTo);
+    engine->addCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThan);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -77,3 +78,9 @@ CompilerValue *EventBlocks::compileWhenBackdropSwitchesTo(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *EventBlocks::compileWhenGreaterThan(Compiler *compiler)
+{
+    compiler->engine()->addWhenGreaterThanScript(compiler->block());
+    return nullptr;
+}
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 47fc0b7b..1c9d51b2 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -22,6 +22,7 @@ class EventBlocks : public IExtension
         static CompilerValue *compileWhenStageClicked(Compiler *compiler);
         static CompilerValue *compileWhenBroadcastReceived(Compiler *compiler);
         static CompilerValue *compileWhenBackdropSwitchesTo(Compiler *compiler);
+        static CompilerValue *compileWhenGreaterThan(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index 8d19168e..0e3d9236 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -113,3 +113,20 @@ TEST_F(EventBlocksTest, WhenBackdropSwitchesTo)
     EXPECT_CALL(m_engineMock, addBackdropChangeScript(block, block->fieldAt(0).get()));
     compiler.compile(block);
 }
+
+// TODO: Add test for when greater than hat predicate
+
+TEST_F(EventBlocksTest, WhenGreaterThan)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whengreaterthan");
+    builder.addDropdownField("WHENGREATERTHANMENU", "LOUDNESS");
+    builder.addValueInput("VALUE", 8.9);
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addWhenGreaterThanScript(block));
+    compiler.compile(block);
+}

From 4f3d6dc5dc11db906c364c5147ca4e2d17bc7c2e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 16:01:07 +0100
Subject: [PATCH 112/259] ScriptBuilder: Add addEntityInput() method

---
 include/scratchcpp/dev/test/scriptbuilder.h |  5 ++---
 src/dev/test/scriptbuilder.cpp              | 15 +++++++++++++
 test/dev/test_api/scriptbuilder_test.cpp    | 24 +++++++++++++++++----
 3 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h
index 1cf5142b..65a3383c 100644
--- a/include/scratchcpp/dev/test/scriptbuilder.h
+++ b/include/scratchcpp/dev/test/scriptbuilder.h
@@ -4,8 +4,7 @@
 
 #include <vector>
 
-#include "../../global.h"
-#include "../../spimpl.h"
+#include "../../inputvalue.h"
 
 namespace libscratchcpp
 {
@@ -13,7 +12,6 @@ namespace libscratchcpp
 class IExtension;
 class IEngine;
 class Target;
-class Entity;
 class List;
 
 } // namespace libscratchcpp
@@ -45,6 +43,7 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
         void addDropdownInput(const std::string &name, const std::string &selectedValue);
         void addDropdownField(const std::string &name, const std::string &selectedValue);
 
+        void addEntityInput(const std::string &name, const std::string &entityName, InputValue::Type entityType, std::shared_ptr<Entity> entity);
         void addEntityField(const std::string &name, std::shared_ptr<Entity> entity);
 
         std::shared_ptr<Block> currentBlock();
diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index 1bdcd9f0..97d3aac2 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -3,6 +3,7 @@
 #include <scratchcpp/dev/test/scriptbuilder.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/input.h>
+#include <scratchcpp/inputvalue.h>
 #include <scratchcpp/field.h>
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/dev/executablecode.h>
@@ -152,6 +153,20 @@ void ScriptBuilder::addDropdownField(const std::string &name, const std::string
     impl->lastBlock->addField(field);
 }
 
+/*! Adds an input pointing to an entity (variable, list, broadcast, etc.) */
+void ScriptBuilder::addEntityInput(const std::string &name, const std::string &entityName, InputValue::Type entityType, std::shared_ptr<Entity> entity)
+{
+    if (!impl->lastBlock)
+        return;
+
+    entity->setId(std::to_string(impl->blockId++));
+    auto input = std::make_shared<Input>(name, Input::Type::Shadow);
+    input->setPrimaryValue(entityName);
+    input->primaryValue()->setValuePtr(entity);
+    input->primaryValue()->setType(entityType);
+    impl->lastBlock->addInput(input);
+}
+
 /*! Adds a field pointing to an entity (variable, list, broadcast, etc.) */
 void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptr<Entity> entity)
 {
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index 45fafd4e..a2d4eb0e 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -162,19 +162,35 @@ TEST_F(ScriptBuilderTest, AddDropdownField)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), "hello\n");
 }
 
+TEST_F(ScriptBuilderTest, AddEntityInput)
+{
+    auto broadcast = std::make_shared<Broadcast>("", "");
+    m_engine->setBroadcasts({ broadcast });
+
+    m_builder->addBlock("test_simple");
+    m_builder->addEntityInput("BROADCAST", "test", InputValue::Type::Broadcast, broadcast);
+    auto block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_simple");
+    ASSERT_EQ(block->inputs().size(), 1);
+    ASSERT_EQ(block->inputAt(0)->name(), "BROADCAST");
+    ASSERT_EQ(block->inputAt(0)->primaryValue()->valuePtr(), broadcast);
+    ASSERT_EQ(block->inputAt(0)->primaryValue()->type(), InputValue::Type::Broadcast);
+}
+
 TEST_F(ScriptBuilderTest, AddEntityField)
 {
     auto broadcast = std::make_shared<Broadcast>("", "");
     m_engine->setBroadcasts({ broadcast });
 
-    m_builder->addBlock("test_print_field");
-    m_builder->addEntityField("STRING", broadcast);
+    m_builder->addBlock("test_simple");
+    m_builder->addEntityField("BROADCAST", broadcast);
     auto block = m_builder->currentBlock();
     ASSERT_TRUE(block);
-    ASSERT_EQ(block->opcode(), "test_print_field");
+    ASSERT_EQ(block->opcode(), "test_simple");
     ASSERT_TRUE(block->inputs().empty());
     ASSERT_EQ(block->fields().size(), 1);
-    ASSERT_EQ(block->fieldAt(0)->name(), "STRING");
+    ASSERT_EQ(block->fieldAt(0)->name(), "BROADCAST");
     ASSERT_EQ(block->fieldAt(0)->valuePtr(), broadcast);
 }
 

From 4fb010b0ee671af4c7c6338467f0635b819cdf60 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 23:33:06 +0100
Subject: [PATCH 113/259] Use Thread instead of Target in ExecutionContext
 constructor

---
 include/scratchcpp/dev/executablecode.h       |   4 +-
 include/scratchcpp/dev/executioncontext.h     |   5 +-
 src/dev/engine/executioncontext.cpp           |  11 +-
 src/dev/engine/executioncontext_p.cpp         |   4 +-
 src/dev/engine/executioncontext_p.h           |   5 +-
 .../internal/llvm/llvmexecutablecode.cpp      |   7 +-
 .../engine/internal/llvm/llvmexecutablecode.h |   2 +-
 .../internal/llvm/llvmexecutioncontext.cpp    |   4 +-
 .../internal/llvm/llvmexecutioncontext.h      |   2 +-
 src/engine/thread.cpp                         |   8 +-
 .../executioncontext_test.cpp                 |  11 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 181 ++++++++++++++----
 test/dev/llvm/llvmexecutablecode_test.cpp     | 130 +++++++------
 test/dev/llvm/llvmexecutioncontext_test.cpp   |   8 +-
 test/mocks/executablecodemock.h               |   2 +-
 test/thread/thread_test.cpp                   |   8 +-
 16 files changed, 266 insertions(+), 126 deletions(-)

diff --git a/include/scratchcpp/dev/executablecode.h b/include/scratchcpp/dev/executablecode.h
index ba5d03e4..8136e40e 100644
--- a/include/scratchcpp/dev/executablecode.h
+++ b/include/scratchcpp/dev/executablecode.h
@@ -10,7 +10,7 @@ namespace libscratchcpp
 {
 
 class ExecutionContext;
-class Target;
+class Thread;
 
 /*! \brief The ExecutableCode class represents the code of a compiled Scratch script. */
 class LIBSCRATCHCPP_EXPORT ExecutableCode
@@ -31,7 +31,7 @@ class LIBSCRATCHCPP_EXPORT ExecutableCode
         virtual bool isFinished(ExecutionContext *context) const = 0;
 
         /*! Creates an execution context for the given Target. */
-        virtual std::shared_ptr<ExecutionContext> createExecutionContext(Target *target) const = 0;
+        virtual std::shared_ptr<ExecutionContext> createExecutionContext(Thread *thread) const = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h
index 4651d079..d56f703f 100644
--- a/include/scratchcpp/dev/executioncontext.h
+++ b/include/scratchcpp/dev/executioncontext.h
@@ -8,6 +8,7 @@
 namespace libscratchcpp
 {
 
+class Thread;
 class Target;
 class Promise;
 class ExecutionContextPrivate;
@@ -16,11 +17,11 @@ class ExecutionContextPrivate;
 class LIBSCRATCHCPP_EXPORT ExecutionContext
 {
     public:
-        ExecutionContext(Target *target);
+        ExecutionContext(Thread *thread);
         ExecutionContext(const ExecutionContext &) = delete;
         virtual ~ExecutionContext() { }
 
-        Target *target() const;
+        Thread *thread() const;
 
         std::shared_ptr<Promise> promise() const;
         void setPromise(std::shared_ptr<Promise> promise);
diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp
index 81f8cb06..643335c2 100644
--- a/src/dev/engine/executioncontext.cpp
+++ b/src/dev/engine/executioncontext.cpp
@@ -1,21 +1,22 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include <scratchcpp/dev/executioncontext.h>
+#include <scratchcpp/thread.h>
 
 #include "executioncontext_p.h"
 
 using namespace libscratchcpp;
 
 /*! Constructs ExecutionContext. */
-ExecutionContext::ExecutionContext(Target *target) :
-    impl(spimpl::make_unique_impl<ExecutionContextPrivate>(target))
+ExecutionContext::ExecutionContext(Thread *thread) :
+    impl(spimpl::make_unique_impl<ExecutionContextPrivate>(thread))
 {
 }
 
-/*! Returns the Target of this context. */
-Target *ExecutionContext::target() const
+/*! Returns the thread of this context. */
+Thread *ExecutionContext::thread() const
 {
-    return impl->target;
+    return impl->thread;
 }
 
 /*! Returns the script promise. */
diff --git a/src/dev/engine/executioncontext_p.cpp b/src/dev/engine/executioncontext_p.cpp
index f2efa1e1..bb42caac 100644
--- a/src/dev/engine/executioncontext_p.cpp
+++ b/src/dev/engine/executioncontext_p.cpp
@@ -4,7 +4,7 @@
 
 using namespace libscratchcpp;
 
-ExecutionContextPrivate::ExecutionContextPrivate(Target *target) :
-    target(target)
+ExecutionContextPrivate::ExecutionContextPrivate(Thread *thread) :
+    thread(thread)
 {
 }
diff --git a/src/dev/engine/executioncontext_p.h b/src/dev/engine/executioncontext_p.h
index 19a22e96..19322c13 100644
--- a/src/dev/engine/executioncontext_p.h
+++ b/src/dev/engine/executioncontext_p.h
@@ -7,14 +7,15 @@
 namespace libscratchcpp
 {
 
+class Thread;
 class Target;
 class Promise;
 
 struct ExecutionContextPrivate
 {
-        ExecutionContextPrivate(Target *target);
+        ExecutionContextPrivate(Thread *thread);
 
-        Target *target = nullptr;
+        Thread *thread = nullptr;
         std::shared_ptr<Promise> promise;
 };
 
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
index 944e5d44..20aa9dfc 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
@@ -5,6 +5,7 @@
 #include <scratchcpp/stage.h>
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/dev/promise.h>
+#include <scratchcpp/thread.h>
 #include <llvm/Support/Error.h>
 #include <iostream>
 
@@ -64,7 +65,7 @@ void LLVMExecutableCode::run(ExecutionContext *context)
 
         ctx->setFinished(done);
     } else {
-        Target *target = ctx->target();
+        Target *target = ctx->thread()->target();
         void *handle = m_mainFunction(context, target, target->variableData(), target->listData());
 
         if (!handle)
@@ -95,9 +96,9 @@ bool LLVMExecutableCode::isFinished(ExecutionContext *context) const
     return getContext(context)->finished();
 }
 
-std::shared_ptr<ExecutionContext> LLVMExecutableCode::createExecutionContext(Target *target) const
+std::shared_ptr<ExecutionContext> LLVMExecutableCode::createExecutionContext(Thread *thread) const
 {
-    return std::make_shared<LLVMExecutionContext>(target);
+    return std::make_shared<LLVMExecutionContext>(thread);
 }
 
 uint64_t LLVMExecutableCode::lookupFunction(const std::string &name)
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h
index ecdc09c3..6d293b3d 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.h
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.h
@@ -25,7 +25,7 @@ class LLVMExecutableCode : public ExecutableCode
 
         bool isFinished(ExecutionContext *context) const override;
 
-        std::shared_ptr<ExecutionContext> createExecutionContext(Target *target) const override;
+        std::shared_ptr<ExecutionContext> createExecutionContext(Thread *thread) const override;
 
     private:
         uint64_t lookupFunction(const std::string &name);
diff --git a/src/dev/engine/internal/llvm/llvmexecutioncontext.cpp b/src/dev/engine/internal/llvm/llvmexecutioncontext.cpp
index 5934a90a..97696b34 100644
--- a/src/dev/engine/internal/llvm/llvmexecutioncontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmexecutioncontext.cpp
@@ -4,8 +4,8 @@
 
 using namespace libscratchcpp;
 
-LLVMExecutionContext::LLVMExecutionContext(Target *target) :
-    ExecutionContext(target)
+LLVMExecutionContext::LLVMExecutionContext(Thread *thread) :
+    ExecutionContext(thread)
 {
 }
 
diff --git a/src/dev/engine/internal/llvm/llvmexecutioncontext.h b/src/dev/engine/internal/llvm/llvmexecutioncontext.h
index c6357819..c3c952fc 100644
--- a/src/dev/engine/internal/llvm/llvmexecutioncontext.h
+++ b/src/dev/engine/internal/llvm/llvmexecutioncontext.h
@@ -10,7 +10,7 @@ namespace libscratchcpp
 class LLVMExecutionContext : public ExecutionContext
 {
     public:
-        LLVMExecutionContext(Target *target);
+        LLVMExecutionContext(Thread *thread);
 
         void *coroutineHandle() const;
         void setCoroutineHandle(void *newCoroutineHandle);
diff --git a/src/engine/thread.cpp b/src/engine/thread.cpp
index 3c1cbfc5..bf34cdab 100644
--- a/src/engine/thread.cpp
+++ b/src/engine/thread.cpp
@@ -18,8 +18,12 @@ Thread::Thread(Target *target, IEngine *engine, Script *script) :
 {
     impl->vm = std::make_unique<VirtualMachine>(target, engine, script, this);
 #ifdef USE_LLVM
-    impl->code = impl->script->code();
-    impl->executionContext = impl->code->createExecutionContext(target);
+    if (impl->script) {
+        impl->code = impl->script->code();
+
+        if (impl->code)
+            impl->executionContext = impl->code->createExecutionContext(this);
+    }
 #endif
 }
 
diff --git a/test/dev/executioncontext/executioncontext_test.cpp b/test/dev/executioncontext/executioncontext_test.cpp
index d1145373..7f4eac6b 100644
--- a/test/dev/executioncontext/executioncontext_test.cpp
+++ b/test/dev/executioncontext/executioncontext_test.cpp
@@ -1,5 +1,5 @@
 #include <scratchcpp/dev/executioncontext.h>
-#include <scratchcpp/target.h>
+#include <scratchcpp/thread.h>
 #include <scratchcpp/dev/promise.h>
 
 #include "../../common.h"
@@ -8,15 +8,14 @@ using namespace libscratchcpp;
 
 TEST(ExecutionContextTest, Constructor)
 {
-    Target target;
-    ExecutionContext ctx(&target);
-    ASSERT_EQ(ctx.target(), &target);
+    Thread thread(nullptr, nullptr, nullptr);
+    ExecutionContext ctx(&thread);
+    ASSERT_EQ(ctx.thread(), &thread);
 }
 
 TEST(ExecutionContextTest, Promise)
 {
-    Target target;
-    ExecutionContext ctx(&target);
+    ExecutionContext ctx(nullptr);
     ASSERT_EQ(ctx.promise(), nullptr);
 
     auto promise = std::make_shared<Promise>();
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index e624fb9c..a51729d6 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1,5 +1,7 @@
 #include <scratchcpp/value.h>
 #include <scratchcpp/dev/executablecode.h>
+#include <scratchcpp/script.h>
+#include <scratchcpp/thread.h>
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/stage.h>
 #include <scratchcpp/variable.h>
@@ -243,7 +245,10 @@ class LLVMCodeBuilderTest : public testing::Test
             std::string expected = str + str;
 
             auto code = m_builder->finalize();
-            auto ctx = code->createExecutionContext(&m_target);
+            Script script(&m_target, nullptr, nullptr);
+            script.setCode(code);
+            Thread thread(&m_target, nullptr, &script);
+            auto ctx = code->createExecutionContext(&thread);
 
             testing::internal::CaptureStdout();
             code->run(ctx.get());
@@ -269,7 +274,10 @@ class LLVMCodeBuilderTest : public testing::Test
             std::string expected = str + str;
 
             auto code = m_builder->finalize();
-            auto ctx = code->createExecutionContext(&m_target);
+            Script script(&m_target, nullptr, nullptr);
+            script.setCode(code);
+            Thread thread(&m_target, nullptr, &script);
+            auto ctx = code->createExecutionContext(&thread);
 
             testing::internal::CaptureStdout();
             code->run(ctx.get());
@@ -296,7 +304,10 @@ class LLVMCodeBuilderTest : public testing::Test
             std::string expected = str + str;
 
             auto code = m_builder->finalize();
-            auto ctx = code->createExecutionContext(&m_target);
+            Script script(&m_target, nullptr, nullptr);
+            script.setCode(code);
+            Thread thread(&m_target, nullptr, &script);
+            auto ctx = code->createExecutionContext(&thread);
 
             testing::internal::CaptureStdout();
             code->run(ctx.get());
@@ -342,7 +353,10 @@ TEST_F(LLVMCodeBuilderTest, FunctionCalls)
             { v, v1, v2 });
         m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
         auto code = m_builder->finalize();
-        auto ctx = code->createExecutionContext(&m_target);
+        Script script(&m_target, nullptr, nullptr);
+        script.setCode(code);
+        Thread thread(&m_target, nullptr, &script);
+        auto ctx = code->createExecutionContext(&thread);
 
         std::stringstream s;
         s << ctx.get();
@@ -393,7 +407,10 @@ TEST_F(LLVMCodeBuilderTest, ConstCasting)
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&m_target);
+    Script script(&m_target, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&m_target, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
 
     static const std::string expected =
         "5.2\n"
@@ -479,7 +496,10 @@ TEST_F(LLVMCodeBuilderTest, RawValueCasting)
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&m_target);
+    Script script(&m_target, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&m_target, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
 
     static const std::string expected =
         "5.2\n"
@@ -1384,7 +1404,11 @@ TEST_F(LLVMCodeBuilderTest, WriteVariable)
     m_builder->createVariableWrite(localVar3.get(), v);
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
 
     ASSERT_EQ(globalVar1->value(), 5);
@@ -1463,7 +1487,11 @@ TEST_F(LLVMCodeBuilderTest, Select)
 
     auto code = m_builder->finalize();
     testing::internal::CaptureStdout();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
@@ -1519,7 +1547,11 @@ TEST_F(LLVMCodeBuilderTest, ReadVariable)
     expected += localVar3->value().toString() + '\n';
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -1587,7 +1619,11 @@ TEST_F(LLVMCodeBuilderTest, ClearList)
     m_builder->createListClear(localList2.get());
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
 
     ASSERT_TRUE(globalList1->empty());
@@ -1634,7 +1670,11 @@ TEST_F(LLVMCodeBuilderTest, RemoveFromList)
     m_builder->createListRemove(localList.get(), v);
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
 
     ASSERT_EQ(globalList->toString(), "13");
@@ -1690,7 +1730,11 @@ TEST_F(LLVMCodeBuilderTest, AppendToList)
     m_builder->createListAppend(localList.get(), v);
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
 
     ASSERT_EQ(globalList->toString(), "1 2 3 1 test");
@@ -1752,7 +1796,11 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
     m_builder->createListInsert(localList.get(), v1, v2);
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
 
     ASSERT_EQ(globalList->toString(), "1 2 1 test 3");
@@ -1808,7 +1856,11 @@ TEST_F(LLVMCodeBuilderTest, ListReplace)
     m_builder->createListReplace(localList.get(), v1, v2);
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
 
     ASSERT_EQ(globalList->toString(), "1 test 1");
@@ -1854,7 +1906,11 @@ TEST_F(LLVMCodeBuilderTest, GetListContents)
         "Lorem ipsum dolor sit\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -1923,7 +1979,11 @@ TEST_F(LLVMCodeBuilderTest, GetListItem)
         "sit\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -1968,7 +2028,11 @@ TEST_F(LLVMCodeBuilderTest, GetListSize)
         "4\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -2059,7 +2123,11 @@ TEST_F(LLVMCodeBuilderTest, GetListItemIndex)
         "-1\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -2150,7 +2218,11 @@ TEST_F(LLVMCodeBuilderTest, ListContainsItem)
         "false\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -2192,7 +2264,10 @@ TEST_F(LLVMCodeBuilderTest, Yield)
     build();
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&m_target);
+    Script script(&m_target, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&m_target, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
 
     static const std::string expected1 =
         "no_args\n"
@@ -2221,7 +2296,7 @@ TEST_F(LLVMCodeBuilderTest, Yield)
     createBuilder(true);
     build();
     code = m_builder->finalize();
-    ctx = code->createExecutionContext(&m_target);
+    ctx = code->createExecutionContext(&thread);
 
     static const std::string expected =
         "no_args\n"
@@ -2273,7 +2348,11 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
         "-4.8\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
     ASSERT_FALSE(code->isFinished(ctx.get()));
 
@@ -2361,7 +2440,11 @@ TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend)
         "hello\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
     ASSERT_FALSE(code->isFinished(ctx.get()));
 
@@ -2523,7 +2606,10 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     m_builder->endIf();
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&m_target);
+    Script script(&m_target, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&m_target, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
 
     static const std::string expected =
         "no_args\n"
@@ -2634,7 +2720,11 @@ TEST_F(LLVMCodeBuilderTest, IfStatementVariables)
         "true\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -2771,7 +2861,11 @@ TEST_F(LLVMCodeBuilderTest, IfStatementLists)
         "false\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -2842,7 +2936,10 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     m_builder->endLoop();
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&m_target);
+    Script script(&m_target, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&m_target, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
 
     static const std::string expected =
         "no_args\n"
@@ -2882,7 +2979,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     m_builder->endLoop();
 
     code = m_builder->finalize();
-    ctx = code->createExecutionContext(&m_target);
+    ctx = code->createExecutionContext(&thread);
 
     static const std::string expected1 = "no_args\n";
 
@@ -2908,7 +3005,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     m_builder->endLoop();
 
     code = m_builder->finalize();
-    ctx = code->createExecutionContext(&m_target);
+    ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
     ASSERT_TRUE(code->isFinished(ctx.get()));
 }
@@ -2976,7 +3073,10 @@ TEST_F(LLVMCodeBuilderTest, WhileLoop)
     m_builder->endLoop();
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&m_target);
+    Script script(&m_target, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&m_target, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
 
     static const std::string expected =
         "1_arg 0\n"
@@ -3001,7 +3101,7 @@ TEST_F(LLVMCodeBuilderTest, WhileLoop)
     m_builder->endLoop();
 
     code = m_builder->finalize();
-    ctx = code->createExecutionContext(&m_target);
+    ctx = code->createExecutionContext(&thread);
 
     static const std::string expected1 = "no_args\n";
 
@@ -3081,7 +3181,10 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
     m_builder->endLoop();
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&m_target);
+    Script script(&m_target, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&m_target, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
 
     static const std::string expected =
         "1_arg 0\n"
@@ -3106,7 +3209,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
     m_builder->endLoop();
 
     code = m_builder->finalize();
-    ctx = code->createExecutionContext(&m_target);
+    ctx = code->createExecutionContext(&thread);
 
     static const std::string expected1 = "no_args\n";
 
@@ -3244,7 +3347,11 @@ TEST_F(LLVMCodeBuilderTest, LoopVariables)
         "true\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
@@ -3434,7 +3541,11 @@ TEST_F(LLVMCodeBuilderTest, LoopLists)
         "false\n";
 
     auto code = m_builder->finalize();
-    auto ctx = code->createExecutionContext(&sprite);
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    ;
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp
index def0f985..581a3a58 100644
--- a/test/dev/llvm/llvmexecutablecode_test.cpp
+++ b/test/dev/llvm/llvmexecutablecode_test.cpp
@@ -3,11 +3,13 @@
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
 #include <scratchcpp/dev/promise.h>
+#include <scratchcpp/thread.h>
+#include <scratchcpp/script.h>
 #include <dev/engine/internal/llvm/llvmexecutablecode.h>
 #include <dev/engine/internal/llvm/llvmexecutioncontext.h>
 #include <llvm/Support/TargetSelect.h>
 #include <llvm/IR/IRBuilder.h>
-#include <gmock/gmock.h>
+#include <enginemock.h>
 
 #include "testmock.h"
 #include "testfunctions.h"
@@ -23,6 +25,8 @@ class LLVMExecutableCodeTest : public testing::Test
             m_builder = std::make_unique<llvm::IRBuilder<>>(m_ctx);
             test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency
 
+            m_script = std::make_unique<Script>(&m_target, nullptr, &m_engine);
+
             llvm::InitializeNativeTarget();
             llvm::InitializeNativeTargetAsmPrinter();
             llvm::InitializeNativeTargetAsmParser();
@@ -77,6 +81,8 @@ class LLVMExecutableCodeTest : public testing::Test
         std::unique_ptr<llvm::Module> m_module;
         std::unique_ptr<llvm::IRBuilder<>> m_builder;
         Target m_target;
+        EngineMock m_engine;
+        std::unique_ptr<Script> m_script;
         TestMock m_mock;
 };
 
@@ -88,10 +94,12 @@ TEST_F(LLVMExecutableCodeTest, CreateExecutionContext)
     beginResumeFunction();
     endFunction(m_builder->getInt1(true));
 
-    LLVMExecutableCode code(std::move(m_module));
-    auto ctx = code.createExecutionContext(&m_target);
+    auto code = std::make_shared<LLVMExecutableCode>(std::move(m_module));
+    m_script->setCode(code);
+    Thread thread(&m_target, &m_engine, m_script.get());
+    auto ctx = code->createExecutionContext(&thread);
     ASSERT_TRUE(ctx);
-    ASSERT_EQ(ctx->target(), &m_target);
+    ASSERT_EQ(ctx->thread(), &thread);
     ASSERT_TRUE(dynamic_cast<LLVMExecutionContext *>(ctx.get()));
 }
 
@@ -107,43 +115,48 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
     beginResumeFunction();
     endFunction(m_builder->getInt1(true));
 
-    LLVMExecutableCode code(std::move(m_module));
-    auto ctx = code.createExecutionContext(&m_target);
-    ASSERT_FALSE(code.isFinished(ctx.get()));
+    auto code = std::make_shared<LLVMExecutableCode>(std::move(m_module));
+    m_script->setCode(code);
+    Thread thread(&m_target, &m_engine, m_script.get());
+    auto ctx = code->createExecutionContext(&thread);
+    ASSERT_FALSE(code->isFinished(ctx.get()));
 
     EXPECT_CALL(m_mock, f(ctx.get(), &m_target, m_target.variableData(), m_target.listData()));
-    code.run(ctx.get());
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->run(ctx.get());
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
-    code.kill(ctx.get());
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->kill(ctx.get());
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
-    code.reset(ctx.get());
-    ASSERT_FALSE(code.isFinished(ctx.get()));
+    code->reset(ctx.get());
+    ASSERT_FALSE(code->isFinished(ctx.get()));
 
-    code.kill(ctx.get());
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->kill(ctx.get());
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
     EXPECT_CALL(m_mock, f).Times(0);
-    code.run(ctx.get());
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->run(ctx.get());
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
     // Test with another context
     Target anotherTarget;
     anotherTarget.addVariable(std::make_shared<Variable>("", ""));
     anotherTarget.addList(std::make_shared<List>("", ""));
-    auto anotherCtx = code.createExecutionContext(&anotherTarget);
-    ASSERT_FALSE(code.isFinished(anotherCtx.get()));
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    Script script(&anotherTarget, nullptr, &m_engine);
+    script.setCode(code);
+    Thread anotherThread(&anotherTarget, &m_engine, &script);
+    auto anotherCtx = code->createExecutionContext(&anotherThread);
+    ASSERT_FALSE(code->isFinished(anotherCtx.get()));
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
     EXPECT_CALL(m_mock, f(anotherCtx.get(), &anotherTarget, anotherTarget.variableData(), anotherTarget.listData()));
-    code.run(anotherCtx.get());
-    ASSERT_TRUE(code.isFinished(anotherCtx.get()));
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->run(anotherCtx.get());
+    ASSERT_TRUE(code->isFinished(anotherCtx.get()));
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
-    code.reset(ctx.get());
-    ASSERT_TRUE(code.isFinished(anotherCtx.get()));
-    ASSERT_FALSE(code.isFinished(ctx.get()));
+    code->reset(ctx.get());
+    ASSERT_TRUE(code->isFinished(anotherCtx.get()));
+    ASSERT_FALSE(code->isFinished(ctx.get()));
 }
 
 TEST_F(LLVMExecutableCodeTest, Promise)
@@ -155,9 +168,11 @@ TEST_F(LLVMExecutableCodeTest, Promise)
     beginResumeFunction();
     endFunction(m_builder->getInt1(true));
 
-    LLVMExecutableCode code(std::move(m_module));
-    auto ctx = code.createExecutionContext(&m_target);
-    ASSERT_FALSE(code.isFinished(ctx.get()));
+    auto code = std::make_shared<LLVMExecutableCode>(std::move(m_module));
+    m_script->setCode(code);
+    Thread thread(&m_target, &m_engine, m_script.get());
+    auto ctx = code->createExecutionContext(&thread);
+    ASSERT_FALSE(code->isFinished(ctx.get()));
 
     // run()
     auto promise = std::make_shared<Promise>();
@@ -165,17 +180,17 @@ TEST_F(LLVMExecutableCodeTest, Promise)
     EXPECT_CALL(m_mock, f).Times(0);
 
     for (int i = 0; i < 10; i++) {
-        code.run(ctx.get());
-        ASSERT_FALSE(code.isFinished(ctx.get()));
+        code->run(ctx.get());
+        ASSERT_FALSE(code->isFinished(ctx.get()));
     }
 
     promise->resolve();
 
     EXPECT_CALL(m_mock, f);
-    code.run(ctx.get());
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->run(ctx.get());
+    ASSERT_TRUE(code->isFinished(ctx.get()));
     ASSERT_EQ(ctx->promise(), nullptr);
-    code.reset(ctx.get());
+    code->reset(ctx.get());
 
     // kill()
     promise = std::make_shared<Promise>();
@@ -183,14 +198,14 @@ TEST_F(LLVMExecutableCodeTest, Promise)
     EXPECT_CALL(m_mock, f).Times(0);
 
     for (int i = 0; i < 10; i++) {
-        code.run(ctx.get());
-        ASSERT_FALSE(code.isFinished(ctx.get()));
+        code->run(ctx.get());
+        ASSERT_FALSE(code->isFinished(ctx.get()));
     }
 
-    code.kill(ctx.get());
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->kill(ctx.get());
+    ASSERT_TRUE(code->isFinished(ctx.get()));
     ASSERT_EQ(ctx->promise(), nullptr);
-    code.reset(ctx.get());
+    code->reset(ctx.get());
 
     // reset()
     promise = std::make_shared<Promise>();
@@ -198,41 +213,44 @@ TEST_F(LLVMExecutableCodeTest, Promise)
     EXPECT_CALL(m_mock, f).Times(0);
 
     for (int i = 0; i < 10; i++) {
-        code.run(ctx.get());
-        ASSERT_FALSE(code.isFinished(ctx.get()));
+        code->run(ctx.get());
+        ASSERT_FALSE(code->isFinished(ctx.get()));
     }
 
-    code.reset(ctx.get());
-    ASSERT_FALSE(code.isFinished(ctx.get()));
+    code->reset(ctx.get());
+    ASSERT_FALSE(code->isFinished(ctx.get()));
     ASSERT_EQ(ctx->promise(), nullptr);
 
     EXPECT_CALL(m_mock, f);
-    code.run(ctx.get());
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->run(ctx.get());
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
     // Test with another context
     Target anotherTarget;
-    auto anotherCtx = code.createExecutionContext(&anotherTarget);
-    ASSERT_FALSE(code.isFinished(anotherCtx.get()));
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    Script script(&anotherTarget, nullptr, &m_engine);
+    script.setCode(code);
+    Thread anotherThread(&anotherTarget, &m_engine, &script);
+    auto anotherCtx = code->createExecutionContext(&anotherThread);
+    ASSERT_FALSE(code->isFinished(anotherCtx.get()));
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
     promise = std::make_shared<Promise>();
     anotherCtx->setPromise(promise);
     EXPECT_CALL(m_mock, f).Times(0);
 
     for (int i = 0; i < 10; i++) {
-        code.run(anotherCtx.get());
-        ASSERT_FALSE(code.isFinished(anotherCtx.get()));
+        code->run(anotherCtx.get());
+        ASSERT_FALSE(code->isFinished(anotherCtx.get()));
     }
 
     promise->resolve();
 
     EXPECT_CALL(m_mock, f);
-    code.run(anotherCtx.get());
-    ASSERT_TRUE(code.isFinished(anotherCtx.get()));
-    ASSERT_TRUE(code.isFinished(ctx.get()));
+    code->run(anotherCtx.get());
+    ASSERT_TRUE(code->isFinished(anotherCtx.get()));
+    ASSERT_TRUE(code->isFinished(ctx.get()));
 
-    code.reset(ctx.get());
-    ASSERT_TRUE(code.isFinished(anotherCtx.get()));
-    ASSERT_FALSE(code.isFinished(ctx.get()));
+    code->reset(ctx.get());
+    ASSERT_TRUE(code->isFinished(anotherCtx.get()));
+    ASSERT_FALSE(code->isFinished(ctx.get()));
 }
diff --git a/test/dev/llvm/llvmexecutioncontext_test.cpp b/test/dev/llvm/llvmexecutioncontext_test.cpp
index afa6a449..f4c5dac1 100644
--- a/test/dev/llvm/llvmexecutioncontext_test.cpp
+++ b/test/dev/llvm/llvmexecutioncontext_test.cpp
@@ -1,4 +1,4 @@
-#include <scratchcpp/target.h>
+#include <scratchcpp/thread.h>
 #include <dev/engine/internal/llvm/llvmexecutioncontext.h>
 #include <gtest/gtest.h>
 
@@ -6,7 +6,7 @@ using namespace libscratchcpp;
 
 TEST(LLVMExecutionContextTest, Constructor)
 {
-    Target target;
-    LLVMExecutionContext ctx(&target);
-    ASSERT_EQ(ctx.target(), &target);
+    Thread thread(nullptr, nullptr, nullptr);
+    LLVMExecutionContext ctx(&thread);
+    ASSERT_EQ(ctx.thread(), &thread);
 }
diff --git a/test/mocks/executablecodemock.h b/test/mocks/executablecodemock.h
index 0958c391..0189ff55 100644
--- a/test/mocks/executablecodemock.h
+++ b/test/mocks/executablecodemock.h
@@ -14,5 +14,5 @@ class ExecutableCodeMock : public ExecutableCode
 
         MOCK_METHOD(bool, isFinished, (ExecutionContext *), (const, override));
 
-        MOCK_METHOD(std::shared_ptr<ExecutionContext>, createExecutionContext, (Target *), (const, override));
+        MOCK_METHOD(std::shared_ptr<ExecutionContext>, createExecutionContext, (Thread *), (const, override));
 };
diff --git a/test/thread/thread_test.cpp b/test/thread/thread_test.cpp
index 65114c0d..ad01084f 100644
--- a/test/thread/thread_test.cpp
+++ b/test/thread/thread_test.cpp
@@ -12,6 +12,8 @@
 using namespace libscratchcpp;
 
 using ::testing::Return;
+using ::testing::Invoke;
+using ::testing::_;
 
 class ThreadTest : public testing::Test
 {
@@ -22,8 +24,10 @@ class ThreadTest : public testing::Test
 #ifdef USE_LLVM
             m_code = std::make_shared<ExecutableCodeMock>();
             m_script->setCode(m_code);
-            m_ctx = std::make_shared<ExecutionContext>(&m_target);
-            EXPECT_CALL(*m_code, createExecutionContext(&m_target)).WillOnce(Return(m_ctx));
+            EXPECT_CALL(*m_code, createExecutionContext(_)).WillOnce(Invoke([this](Thread *thread) {
+                m_ctx = std::make_shared<ExecutionContext>(thread);
+                return m_ctx;
+            }));
 #endif
             m_thread = std::make_unique<Thread>(&m_target, &m_engine, m_script.get());
         }

From 11efc63011452732a9f331f485ea56ebc95ff734 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 23:40:52 +0100
Subject: [PATCH 114/259] ExecutionContext: Add engine getter

---
 include/scratchcpp/dev/executioncontext.h           | 3 ++-
 src/dev/engine/executioncontext.cpp                 | 6 ++++++
 test/dev/executioncontext/CMakeLists.txt            | 2 ++
 test/dev/executioncontext/executioncontext_test.cpp | 6 +++++-
 4 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h
index d56f703f..2f41ea65 100644
--- a/include/scratchcpp/dev/executioncontext.h
+++ b/include/scratchcpp/dev/executioncontext.h
@@ -9,7 +9,7 @@ namespace libscratchcpp
 {
 
 class Thread;
-class Target;
+class IEngine;
 class Promise;
 class ExecutionContextPrivate;
 
@@ -22,6 +22,7 @@ class LIBSCRATCHCPP_EXPORT ExecutionContext
         virtual ~ExecutionContext() { }
 
         Thread *thread() const;
+        IEngine *engine() const;
 
         std::shared_ptr<Promise> promise() const;
         void setPromise(std::shared_ptr<Promise> promise);
diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp
index 643335c2..b0e74926 100644
--- a/src/dev/engine/executioncontext.cpp
+++ b/src/dev/engine/executioncontext.cpp
@@ -19,6 +19,12 @@ Thread *ExecutionContext::thread() const
     return impl->thread;
 }
 
+/*! Returns the engine of the project. */
+IEngine *ExecutionContext::engine() const
+{
+    return impl->thread->engine();
+}
+
 /*! Returns the script promise. */
 std::shared_ptr<Promise> ExecutionContext::promise() const
 {
diff --git a/test/dev/executioncontext/CMakeLists.txt b/test/dev/executioncontext/CMakeLists.txt
index ef216403..ace0b73c 100644
--- a/test/dev/executioncontext/CMakeLists.txt
+++ b/test/dev/executioncontext/CMakeLists.txt
@@ -6,7 +6,9 @@ add_executable(
 target_link_libraries(
   executioncontext_test
   GTest::gtest_main
+  GTest::gmock_main
   scratchcpp
+  scratchcpp_mocks
 )
 
 gtest_discover_tests(executioncontext_test)
diff --git a/test/dev/executioncontext/executioncontext_test.cpp b/test/dev/executioncontext/executioncontext_test.cpp
index 7f4eac6b..38122f6f 100644
--- a/test/dev/executioncontext/executioncontext_test.cpp
+++ b/test/dev/executioncontext/executioncontext_test.cpp
@@ -2,15 +2,19 @@
 #include <scratchcpp/thread.h>
 #include <scratchcpp/dev/promise.h>
 
+#include <enginemock.h>
+
 #include "../../common.h"
 
 using namespace libscratchcpp;
 
 TEST(ExecutionContextTest, Constructor)
 {
-    Thread thread(nullptr, nullptr, nullptr);
+    EngineMock engine;
+    Thread thread(nullptr, &engine, nullptr);
     ExecutionContext ctx(&thread);
     ASSERT_EQ(ctx.thread(), &thread);
+    ASSERT_EQ(ctx.engine(), &engine);
 }
 
 TEST(ExecutionContextTest, Promise)

From 12e6418a559654f0c54f56694f9122002b0468b0 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 10 Dec 2024 23:47:18 +0100
Subject: [PATCH 115/259] Implement event_broadcast

---
 src/dev/blocks/eventblocks.cpp        | 20 +++++++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 52 +++++++++++++++++++++++++++
 3 files changed, 73 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 097cf9c4..bc485cc1 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -5,6 +5,8 @@
 #include <scratchcpp/block.h>
 #include <scratchcpp/field.h>
 #include <scratchcpp/broadcast.h>
+#include <scratchcpp/dev/executioncontext.h>
+#include <scratchcpp/thread.h>
 
 #include "eventblocks.h"
 
@@ -29,6 +31,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "event_whenbroadcastreceived", &compileWhenBroadcastReceived);
     engine->addCompileFunction(this, "event_whenbackdropswitchesto", &compileWhenBackdropSwitchesTo);
     engine->addCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThan);
+    engine->addCompileFunction(this, "event_broadcast", &compileBroadcast);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -84,3 +87,20 @@ CompilerValue *EventBlocks::compileWhenGreaterThan(Compiler *compiler)
     compiler->engine()->addWhenGreaterThanScript(compiler->block());
     return nullptr;
 }
+
+CompilerValue *EventBlocks::compileBroadcast(Compiler *compiler)
+{
+    auto input = compiler->addInput("BROADCAST_INPUT");
+    compiler->addFunctionCallWithCtx("event_broadcast", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+    return nullptr;
+}
+
+extern "C" void event_broadcast(ExecutionContext *ctx, const char *name)
+{
+    Thread *thread = ctx->thread();
+    IEngine *engine = thread->engine();
+    std::vector<int> broadcasts = engine->findBroadcasts(name);
+
+    for (int index : broadcasts)
+        engine->broadcast(index, thread, false);
+}
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 1c9d51b2..85665c2a 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -23,6 +23,7 @@ class EventBlocks : public IExtension
         static CompilerValue *compileWhenBroadcastReceived(Compiler *compiler);
         static CompilerValue *compileWhenBackdropSwitchesTo(Compiler *compiler);
         static CompilerValue *compileWhenGreaterThan(Compiler *compiler);
+        static CompilerValue *compileBroadcast(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index 0e3d9236..8ab84b5e 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -4,6 +4,9 @@
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/broadcast.h>
 #include <scratchcpp/block.h>
+#include <scratchcpp/script.h>
+#include <scratchcpp/thread.h>
+#include <scratchcpp/dev/executablecode.h>
 #include <enginemock.h>
 
 #include "../common.h"
@@ -12,6 +15,8 @@
 using namespace libscratchcpp;
 using namespace libscratchcpp::test;
 
+using ::testing::Return;
+
 class EventBlocksTest : public testing::Test
 {
     public:
@@ -130,3 +135,50 @@ TEST_F(EventBlocksTest, WhenGreaterThan)
     EXPECT_CALL(m_engineMock, addWhenGreaterThanScript(block));
     compiler.compile(block);
 }
+
+TEST_F(EventBlocksTest, Broadcast)
+{
+    auto broadcast = std::make_shared<Broadcast>("", "test");
+    m_engine->setBroadcasts({ broadcast });
+
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_broadcast");
+    builder.addEntityInput("BROADCAST_INPUT", "test", InputValue::Type::Broadcast, broadcast);
+    auto block1 = builder.currentBlock();
+
+    builder.addBlock("event_broadcast");
+    builder.addNullObscuredInput("BROADCAST_INPUT");
+    auto block2 = builder.currentBlock();
+
+    block1->setNext(nullptr);
+    block2->setParent(nullptr);
+
+    {
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block1);
+        Script script(target.get(), block1, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector<int>({ 1, 4 })));
+        EXPECT_CALL(m_engineMock, broadcast(1, &thread, false));
+        EXPECT_CALL(m_engineMock, broadcast(4, &thread, false));
+        thread.run();
+    }
+
+    {
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block2);
+        Script script(target.get(), block2, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, findBroadcasts("0")).WillOnce(Return(std::vector<int>({ 5, 7, 8 })));
+        EXPECT_CALL(m_engineMock, broadcast(5, &thread, false));
+        EXPECT_CALL(m_engineMock, broadcast(7, &thread, false));
+        EXPECT_CALL(m_engineMock, broadcast(8, &thread, false));
+        thread.run();
+    }
+}

From 1f6f89b2edc68758fe7c4f913182d1c33ad233ce Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 00:06:16 +0100
Subject: [PATCH 116/259] Implement event_broadcastandwait

---
 src/dev/blocks/eventblocks.cpp        | 17 ++++++++--
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 47 +++++++++++++++++++++++++++
 3 files changed, 62 insertions(+), 3 deletions(-)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index bc485cc1..6ff3e958 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -7,6 +7,7 @@
 #include <scratchcpp/broadcast.h>
 #include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/thread.h>
+#include <scratchcpp/dev/compilerconstant.h>
 
 #include "eventblocks.h"
 
@@ -32,6 +33,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "event_whenbackdropswitchesto", &compileWhenBackdropSwitchesTo);
     engine->addCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThan);
     engine->addCompileFunction(this, "event_broadcast", &compileBroadcast);
+    engine->addCompileFunction(this, "event_broadcastandwait", &compileBroadcastAndWait);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -91,16 +93,25 @@ CompilerValue *EventBlocks::compileWhenGreaterThan(Compiler *compiler)
 CompilerValue *EventBlocks::compileBroadcast(Compiler *compiler)
 {
     auto input = compiler->addInput("BROADCAST_INPUT");
-    compiler->addFunctionCallWithCtx("event_broadcast", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+    auto wait = compiler->addConstValue(false);
+    compiler->addFunctionCallWithCtx("event_broadcast", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { input, wait });
     return nullptr;
 }
 
-extern "C" void event_broadcast(ExecutionContext *ctx, const char *name)
+CompilerValue *EventBlocks::compileBroadcastAndWait(Compiler *compiler)
+{
+    auto input = compiler->addInput("BROADCAST_INPUT");
+    auto wait = compiler->addConstValue(true);
+    compiler->addFunctionCallWithCtx("event_broadcast", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { input, wait });
+    return nullptr;
+}
+
+extern "C" void event_broadcast(ExecutionContext *ctx, const char *name, bool wait)
 {
     Thread *thread = ctx->thread();
     IEngine *engine = thread->engine();
     std::vector<int> broadcasts = engine->findBroadcasts(name);
 
     for (int index : broadcasts)
-        engine->broadcast(index, thread, false);
+        engine->broadcast(index, thread, wait);
 }
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 85665c2a..28bbf27a 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -24,6 +24,7 @@ class EventBlocks : public IExtension
         static CompilerValue *compileWhenBackdropSwitchesTo(Compiler *compiler);
         static CompilerValue *compileWhenGreaterThan(Compiler *compiler);
         static CompilerValue *compileBroadcast(Compiler *compiler);
+        static CompilerValue *compileBroadcastAndWait(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index 8ab84b5e..5bdc7058 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -182,3 +182,50 @@ TEST_F(EventBlocksTest, Broadcast)
         thread.run();
     }
 }
+
+TEST_F(EventBlocksTest, BroadcastAndWait)
+{
+    auto broadcast = std::make_shared<Broadcast>("", "test");
+    m_engine->setBroadcasts({ broadcast });
+
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_broadcastandwait");
+    builder.addEntityInput("BROADCAST_INPUT", "test", InputValue::Type::Broadcast, broadcast);
+    auto block1 = builder.currentBlock();
+
+    builder.addBlock("event_broadcastandwait");
+    builder.addNullObscuredInput("BROADCAST_INPUT");
+    auto block2 = builder.currentBlock();
+
+    block1->setNext(nullptr);
+    block2->setParent(nullptr);
+
+    {
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block1);
+        Script script(target.get(), block1, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector<int>({ 1, 4 })));
+        EXPECT_CALL(m_engineMock, broadcast(1, &thread, true));
+        EXPECT_CALL(m_engineMock, broadcast(4, &thread, true));
+        thread.run();
+    }
+
+    {
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block2);
+        Script script(target.get(), block2, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, findBroadcasts("0")).WillOnce(Return(std::vector<int>({ 5, 7, 8 })));
+        EXPECT_CALL(m_engineMock, broadcast(5, &thread, true));
+        EXPECT_CALL(m_engineMock, broadcast(7, &thread, true));
+        EXPECT_CALL(m_engineMock, broadcast(8, &thread, true));
+        thread.run();
+    }
+}

From fb4a4221f033aca1509f7ad4446f8874a001515c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 00:11:45 +0100
Subject: [PATCH 117/259] Implement event_whenkeypressed

---
 src/dev/blocks/eventblocks.cpp        | 12 ++++++++++++
 src/dev/blocks/eventblocks.h          |  1 +
 test/dev/blocks/event_blocks_test.cpp | 14 ++++++++++++++
 3 files changed, 27 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 6ff3e958..4189bcfa 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -34,6 +34,7 @@ void EventBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThan);
     engine->addCompileFunction(this, "event_broadcast", &compileBroadcast);
     engine->addCompileFunction(this, "event_broadcastandwait", &compileBroadcastAndWait);
+    engine->addCompileFunction(this, "event_whenkeypressed", &compileWhenKeyPressed);
 }
 
 CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler)
@@ -106,6 +107,17 @@ CompilerValue *EventBlocks::compileBroadcastAndWait(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *EventBlocks::compileWhenKeyPressed(Compiler *compiler)
+{
+    auto block = compiler->block();
+    Field *field = compiler->field("KEY_OPTION");
+
+    if (field)
+        compiler->engine()->addKeyPressScript(block, field);
+
+    return nullptr;
+}
+
 extern "C" void event_broadcast(ExecutionContext *ctx, const char *name, bool wait)
 {
     Thread *thread = ctx->thread();
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 28bbf27a..34454ebc 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -25,6 +25,7 @@ class EventBlocks : public IExtension
         static CompilerValue *compileWhenGreaterThan(Compiler *compiler);
         static CompilerValue *compileBroadcast(Compiler *compiler);
         static CompilerValue *compileBroadcastAndWait(Compiler *compiler);
+        static CompilerValue *compileWhenKeyPressed(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index 5bdc7058..470ccaf0 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -229,3 +229,17 @@ TEST_F(EventBlocksTest, BroadcastAndWait)
         thread.run();
     }
 }
+
+TEST_F(EventBlocksTest, WhenKeyPressed)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("event_whenkeypressed");
+    builder.addDropdownField("KEY_OPTION", "a");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addKeyPressScript(block, block->fieldAt(0).get()));
+    compiler.compile(block);
+}

From d93225779cb9206a23e966d31b9cf2715175874d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 08:44:45 +0100
Subject: [PATCH 118/259] Implement control_forever

---
 src/dev/blocks/controlblocks.cpp        | 15 +++++++
 src/dev/blocks/controlblocks.h          |  3 ++
 test/dev/blocks/CMakeLists.txt          | 13 ++++++
 test/dev/blocks/control_blocks_test.cpp | 57 ++++++++++++++++++++++++-
 test/dev/blocks/util.cpp                | 32 ++++++++++++++
 test/dev/blocks/util.h                  | 11 +++++
 6 files changed, 130 insertions(+), 1 deletion(-)
 create mode 100644 test/dev/blocks/util.cpp
 create mode 100644 test/dev/blocks/util.h

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 937e5943..4f5d4fb1 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -1,5 +1,11 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/value.h>
+#include <scratchcpp/input.h>
+
 #include "controlblocks.h"
 
 using namespace libscratchcpp;
@@ -16,4 +22,13 @@ std::string ControlBlocks::description() const
 
 void ControlBlocks::registerBlocks(IEngine *engine)
 {
+    engine->addCompileFunction(this, "control_forever", &compileForever);
+}
+
+CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
+{
+    auto substack = compiler->input("SUBSTACK");
+    compiler->beginLoopCondition();
+    compiler->moveToWhileLoop(compiler->addConstValue(true), substack ? substack->valueBlock() : nullptr);
+    return nullptr;
 }
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 155f9296..60ad0511 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -14,6 +14,9 @@ class ControlBlocks : public IExtension
         std::string description() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+    private:
+        static CompilerValue *compileForever(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/CMakeLists.txt b/test/dev/blocks/CMakeLists.txt
index 3e32a0d0..3fc113f7 100644
--- a/test/dev/blocks/CMakeLists.txt
+++ b/test/dev/blocks/CMakeLists.txt
@@ -1,3 +1,15 @@
+add_library(
+  block_test_deps SHARED
+  util.cpp
+  util.h
+)
+
+target_link_libraries(
+  block_test_deps
+  GTest::gtest_main
+  scratchcpp
+)
+
 # motion_blocks_test
 if (LIBSCRATCHCPP_ENABLE_MOTION_BLOCKS)
     add_executable(
@@ -83,6 +95,7 @@ if (LIBSCRATCHCPP_ENABLE_CONTROL_BLOCKS)
       GTest::gmock_main
       scratchcpp
       scratchcpp_mocks
+      block_test_deps
     )
 
     gtest_discover_tests(control_blocks_test)
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index b5cff150..f7db1734 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -1,15 +1,70 @@
+#include <scratchcpp/project.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/test/scriptbuilder.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/input.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "dev/blocks/controlblocks.h"
+#include "util.h"
 
 using namespace libscratchcpp;
+using namespace libscratchcpp::test;
 
 class ControlBlocksTest : public testing::Test
 {
     public:
-        void SetUp() override { m_extension = std::make_unique<ControlBlocks>(); }
+        void SetUp() override
+        {
+            m_extension = std::make_unique<ControlBlocks>();
+            m_engine = m_project.engine().get();
+            m_extension->registerBlocks(m_engine);
+            registerBlocks(m_engine, m_extension.get());
+        }
 
         std::unique_ptr<IExtension> m_extension;
+        Project m_project;
+        IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
 };
+
+TEST_F(ControlBlocksTest, Forever)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_forever");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            testing::internal::CaptureStdout();
+            m_engine->step();
+            ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n");
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+        builder.addBlock("control_forever");
+
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            m_engine->step();
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+    }
+}
diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp
new file mode 100644
index 00000000..38258807
--- /dev/null
+++ b/test/dev/blocks/util.cpp
@@ -0,0 +1,32 @@
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/value.h>
+#include <iostream>
+
+#include "util.h"
+
+namespace libscratchcpp
+{
+
+void registerBlocks(IEngine *engine, IExtension *extension)
+{
+    engine->addCompileFunction(extension, "test_print", [](Compiler *compiler) -> CompilerValue * {
+        auto input = compiler->addInput("STRING");
+        compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+        return nullptr;
+    });
+
+    engine->addCompileFunction(extension, "test_print_test", [](Compiler *compiler) -> CompilerValue * {
+        auto input = compiler->addConstValue("test");
+        compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+        return nullptr;
+    });
+}
+
+extern "C" void test_print(const char *str)
+{
+    std::cout << str << std::endl;
+}
+
+} // namespace libscratchcpp
diff --git a/test/dev/blocks/util.h b/test/dev/blocks/util.h
new file mode 100644
index 00000000..c82c34a4
--- /dev/null
+++ b/test/dev/blocks/util.h
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace libscratchcpp
+{
+
+class IEngine;
+class IExtension;
+
+void registerBlocks(IEngine *engine, IExtension *extension);
+
+} // namespace libscratchcpp

From cf1730cf028dccba5c8da68c76fa4e4ed5150ca8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 10:16:36 +0100
Subject: [PATCH 119/259] LLVMCodeBuilder: Fix infinite count in repeat loop

---
 .../engine/internal/llvm/llvmcodebuilder.cpp   |  4 +++-
 test/dev/llvm/llvmcodebuilder_test.cpp         | 18 ++++++++++++++++++
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index da31cea5..f2f2e8fb 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -784,6 +784,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &reg = step.args[0];
                 assert(reg.first == Compiler::StaticType::Number);
                 llvm::Value *count = castValue(reg.second, reg.first);
+                llvm::Value *isInf = m_builder.CreateFCmpOEQ(count, llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false));
 
                 // Clamp count if <= 0 (we can skip the loop if count is not positive)
                 llvm::Value *comparison = m_builder.CreateFCmpULE(count, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)));
@@ -795,6 +796,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, { count->getType() });
                 count = m_builder.CreateCall(roundFunc, { count });
                 count = m_builder.CreateFPToSI(count, m_builder.getInt64Ty()); // cast to signed integer
+                count = m_builder.CreateSelect(isInf, zero, count);
 
                 // Jump to condition branch
                 m_builder.CreateBr(loop.conditionBranch);
@@ -808,7 +810,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func);
 
                 llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index);
-                comparison = m_builder.CreateICmpULT(currentIndex, count);
+                comparison = m_builder.CreateOr(isInf, m_builder.CreateICmpULT(currentIndex, count));
                 m_builder.CreateCondBr(comparison, body, loop.afterLoop);
 
                 // Switch to body branch
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index a51729d6..e3ba1946 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -3008,6 +3008,24 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     ctx = code->createExecutionContext(&thread);
     code->run(ctx.get());
     ASSERT_TRUE(code->isFinished(ctx.get()));
+
+    // Infinite no warp loop
+    createBuilder(false);
+
+    v = m_builder->addConstValue("Infinity");
+    m_builder->beginRepeatLoop(v);
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->endLoop();
+
+    code = m_builder->finalize();
+    ctx = code->createExecutionContext(&thread);
+
+    for (int i = 0; i < 10; i++) {
+        testing::internal::CaptureStdout();
+        code->run(ctx.get());
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "no_args\n");
+        ASSERT_FALSE(code->isFinished(ctx.get()));
+    }
 }
 
 TEST_F(LLVMCodeBuilderTest, WhileLoop)

From 30ae4871b133d7faee119fac2959f8bc0b54585e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 10:16:53 +0100
Subject: [PATCH 120/259] Implement control_repeat

---
 src/dev/blocks/controlblocks.cpp        |  8 ++++++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 37 +++++++++++++++++++++++++
 3 files changed, 46 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 4f5d4fb1..89fff45d 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -23,6 +23,7 @@ std::string ControlBlocks::description() const
 void ControlBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "control_forever", &compileForever);
+    engine->addCompileFunction(this, "control_repeat", &compileRepeat);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -32,3 +33,10 @@ CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
     compiler->moveToWhileLoop(compiler->addConstValue(true), substack ? substack->valueBlock() : nullptr);
     return nullptr;
 }
+
+CompilerValue *ControlBlocks::compileRepeat(Compiler *compiler)
+{
+    auto substack = compiler->input("SUBSTACK");
+    compiler->moveToRepeatLoop(compiler->addInput("TIMES"), substack ? substack->valueBlock() : nullptr);
+    return nullptr;
+}
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 60ad0511..2b3b7c5c 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -17,6 +17,7 @@ class ControlBlocks : public IExtension
 
     private:
         static CompilerValue *compileForever(Compiler *compiler);
+        static CompilerValue *compileRepeat(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index f7db1734..14352acf 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -68,3 +68,40 @@ TEST_F(ControlBlocksTest, Forever)
         }
     }
 }
+
+TEST_F(ControlBlocksTest, Repeat)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_repeat");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        builder.addValueInput("TIMES", 5);
+
+        builder.build();
+
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\ntest\ntest\ntest\ntest\n");
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+        builder.addBlock("control_repeat");
+        builder.addValueInput("TIMES", "Infinity");
+
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            m_engine->step();
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+    }
+}

From 94de97e585a6d05e137acd61766e6685aa7361c4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 10:29:20 +0100
Subject: [PATCH 121/259] Implement control_if

---
 src/dev/blocks/controlblocks.cpp        |  8 ++++++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 37 +++++++++++++++++++++++++
 3 files changed, 46 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 89fff45d..ba7ea851 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -24,6 +24,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "control_forever", &compileForever);
     engine->addCompileFunction(this, "control_repeat", &compileRepeat);
+    engine->addCompileFunction(this, "control_if", &compileIf);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -40,3 +41,10 @@ CompilerValue *ControlBlocks::compileRepeat(Compiler *compiler)
     compiler->moveToRepeatLoop(compiler->addInput("TIMES"), substack ? substack->valueBlock() : nullptr);
     return nullptr;
 }
+
+CompilerValue *ControlBlocks::compileIf(Compiler *compiler)
+{
+    auto substack = compiler->input("SUBSTACK");
+    compiler->moveToIf(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr);
+    return nullptr;
+}
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 2b3b7c5c..bccfe0ad 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -18,6 +18,7 @@ class ControlBlocks : public IExtension
     private:
         static CompilerValue *compileForever(Compiler *compiler);
         static CompilerValue *compileRepeat(Compiler *compiler);
+        static CompilerValue *compileIf(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 14352acf..2e54576d 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -105,3 +105,40 @@ TEST_F(ControlBlocksTest, Repeat)
         }
     }
 }
+
+TEST_F(ControlBlocksTest, If)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_if");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addValueInput("CONDITION", false);
+        builder.addObscuredInput("SUBSTACK", substack);
+
+        builder.addBlock("control_if");
+        substack = std::make_shared<Block>("", "test_print_test");
+        builder.addValueInput("CONDITION", true);
+        builder.addObscuredInput("SUBSTACK", substack);
+
+        builder.build();
+
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+        builder.addBlock("control_if");
+        builder.addValueInput("CONDITION", true);
+
+        builder.build();
+        builder.run();
+    }
+}

From 6784aaf14de8e5191b9dc4e21a2e5f803d5fe15e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 10:37:32 +0100
Subject: [PATCH 122/259] Add obscured input tests to control blocks test

---
 test/dev/blocks/control_blocks_test.cpp | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 2e54576d..109574af 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -81,6 +81,11 @@ TEST_F(ControlBlocksTest, Repeat)
         builder.addObscuredInput("SUBSTACK", substack);
         builder.addValueInput("TIMES", 5);
 
+        builder.addBlock("control_repeat");
+        substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        builder.addNullObscuredInput("TIMES");
+
         builder.build();
 
         testing::internal::CaptureStdout();
@@ -118,6 +123,11 @@ TEST_F(ControlBlocksTest, If)
         builder.addValueInput("CONDITION", false);
         builder.addObscuredInput("SUBSTACK", substack);
 
+        builder.addBlock("control_if");
+        substack = std::make_shared<Block>("", "test_print_test");
+        builder.addNullObscuredInput("CONDITION");
+        builder.addObscuredInput("SUBSTACK", substack);
+
         builder.addBlock("control_if");
         substack = std::make_shared<Block>("", "test_print_test");
         builder.addValueInput("CONDITION", true);

From d2200b998e424cc8693bcab0398a37690e22186c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 10:37:50 +0100
Subject: [PATCH 123/259] Implement control_if_else

---
 src/dev/blocks/controlblocks.cpp        |  9 +++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 98 +++++++++++++++++++++++++
 test/dev/blocks/util.cpp                |  6 ++
 4 files changed, 114 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index ba7ea851..288388e5 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -25,6 +25,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_forever", &compileForever);
     engine->addCompileFunction(this, "control_repeat", &compileRepeat);
     engine->addCompileFunction(this, "control_if", &compileIf);
+    engine->addCompileFunction(this, "control_if_else", &compileIfElse);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -48,3 +49,11 @@ CompilerValue *ControlBlocks::compileIf(Compiler *compiler)
     compiler->moveToIf(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr);
     return nullptr;
 }
+
+CompilerValue *ControlBlocks::compileIfElse(Compiler *compiler)
+{
+    auto substack = compiler->input("SUBSTACK");
+    auto substack2 = compiler->input("SUBSTACK2");
+    compiler->moveToIfElse(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr, substack2 ? substack2->valueBlock() : nullptr);
+    return nullptr;
+}
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index bccfe0ad..b58e7856 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -19,6 +19,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileForever(Compiler *compiler);
         static CompilerValue *compileRepeat(Compiler *compiler);
         static CompilerValue *compileIf(Compiler *compiler);
+        static CompilerValue *compileIfElse(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 109574af..73c9f4db 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -152,3 +152,101 @@ TEST_F(ControlBlocksTest, If)
         builder.run();
     }
 }
+
+TEST_F(ControlBlocksTest, IfElse)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", false);
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        auto substack2 = std::make_shared<Block>("", "test_print_test2");
+        builder.addObscuredInput("SUBSTACK2", substack2);
+
+        builder.addBlock("control_if_else");
+        builder.addNullObscuredInput("CONDITION");
+        substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        substack2 = std::make_shared<Block>("", "test_print_test2");
+        builder.addObscuredInput("SUBSTACK2", substack2);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", true);
+        substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        substack2 = std::make_shared<Block>("", "test_print_test2");
+        builder.addObscuredInput("SUBSTACK2", substack2);
+
+        builder.build();
+
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "test2\ntest2\ntest\n");
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", false);
+        auto substack2 = std::make_shared<Block>("", "test_print_test2");
+        builder.addObscuredInput("SUBSTACK2", substack2);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", true);
+        substack2 = std::make_shared<Block>("", "test_print_test2");
+        builder.addObscuredInput("SUBSTACK2", substack2);
+
+        builder.build();
+
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "test2\n");
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", false);
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", true);
+        substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+
+        builder.build();
+
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", false);
+
+        builder.addBlock("control_if_else");
+        builder.addValueInput("CONDITION", true);
+
+        builder.build();
+        builder.run();
+    }
+}
diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp
index 38258807..4cc5d279 100644
--- a/test/dev/blocks/util.cpp
+++ b/test/dev/blocks/util.cpp
@@ -22,6 +22,12 @@ void registerBlocks(IEngine *engine, IExtension *extension)
         compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
         return nullptr;
     });
+
+    engine->addCompileFunction(extension, "test_print_test2", [](Compiler *compiler) -> CompilerValue * {
+        auto input = compiler->addConstValue("test2");
+        compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
+        return nullptr;
+    });
 }
 
 extern "C" void test_print(const char *str)

From 59daa77aa4c0ab7936664729afd63d50152fcea1 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 17:02:31 +0100
Subject: [PATCH 124/259] LLVMCodeBuilder: Add createStop() method

---
 src/dev/engine/internal/icodebuilder.h        |  2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 16 +++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvminstruction.h    |  3 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 69 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  2 +
 6 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 5132f1a9..3b1f5419 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -81,6 +81,8 @@ class ICodeBuilder
         virtual void endLoop() = 0;
 
         virtual void yield() = 0;
+
+        virtual void createStop() = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index f2f2e8fb..eac2f868 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -68,6 +68,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     llvm::Value *targetLists = func->getArg(3);
 
     llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
+    llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_ctx, "end", func);
     m_builder.SetInsertPoint(entry);
 
     // Init coroutine
@@ -898,9 +899,19 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 popScopeLevel();
                 break;
             }
+
+            case LLVMInstruction::Type::Stop: {
+                m_builder.CreateBr(endBranch);
+                llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_ctx, "", func);
+                m_builder.SetInsertPoint(nextBranch);
+                break;
+            }
         }
     }
 
+    m_builder.CreateBr(endBranch);
+
+    m_builder.SetInsertPoint(endBranch);
     freeHeap();
     syncVariables(targetVariables);
 
@@ -1283,6 +1294,11 @@ void LLVMCodeBuilder::yield()
     m_instructions.push_back({ LLVMInstruction::Type::Yield });
 }
 
+void LLVMCodeBuilder::createStop()
+{
+    m_instructions.push_back({ LLVMInstruction::Type::Stop });
+}
+
 void LLVMCodeBuilder::initTypes()
 {
     m_valueDataType = LLVMTypes::createValueDataType(&m_builder);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 1978ef30..3f96bc0a 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -89,6 +89,8 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void yield() override;
 
+        void createStop() override;
+
     private:
         enum class Comparison
         {
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index b770008e..a016365b 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -61,7 +61,8 @@ struct LLVMInstruction
             BeginWhileLoop,
             BeginRepeatUntilLoop,
             BeginLoopCondition,
-            EndLoop
+            EndLoop,
+            Stop
         };
 
         LLVMInstruction(Type type) :
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index e3ba1946..02bb877d 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -3568,3 +3568,72 @@ TEST_F(LLVMCodeBuilderTest, LoopLists)
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
+
+TEST_F(LLVMCodeBuilderTest, StopNoWarp)
+{
+    Sprite sprite;
+    createBuilder(&sprite, false);
+
+    m_builder->beginLoopCondition();
+    CompilerValue *v = m_builder->addConstValue(true);
+    m_builder->beginWhileLoop(v);
+    m_builder->createStop();
+    m_builder->endLoop();
+
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+
+    std::string expected = "";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, StopWarp)
+{
+    Sprite sprite;
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(true);
+    m_builder->beginIfStatement(v);
+    m_builder->createStop();
+    m_builder->endIf();
+
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+
+    std::string expected = "";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, StopAndReturn)
+{
+    Sprite sprite;
+    createBuilder(&sprite, true);
+
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->createStop();
+
+    std::string expected = "no_args\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index cf1738ea..f66408e7 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -71,4 +71,6 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, endLoop, (), (override));
 
         MOCK_METHOD(void, yield, (), (override));
+
+        MOCK_METHOD(void, createStop, (), (override));
 };

From 5b37719244dacd7de06a2758641f54c5103521c5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 18:20:10 +0100
Subject: [PATCH 125/259] Compiler: Add createStop() method

---
 include/scratchcpp/dev/compiler.h   |  2 ++
 src/dev/engine/compiler.cpp         |  6 ++++++
 test/dev/compiler/compiler_test.cpp | 15 +++++++++++++++
 3 files changed, 23 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 1c1d441e..ec326608 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -115,6 +115,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block> substack);
         void warp();
 
+        void createStop();
+
         Input *input(const std::string &name) const;
         Field *field(const std::string &name) const;
 
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index db4c0b54..5bdeacf7 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -495,6 +495,12 @@ void Compiler::warp()
     impl->warp = true;
 }
 
+/*! Creates a stop script instruction. */
+void Compiler::createStop()
+{
+    impl->builder->createStop();
+}
+
 /*! Convenience method which returns the field with the given name. */
 Input *Compiler::input(const std::string &name) const
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 7e7ed85f..70ab740d 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -1418,6 +1418,21 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop)
     compile(compiler, l1);
 }
 
+TEST_F(CompilerTest, CreateStop)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createStop());
+        compiler->createStop();
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, Input)
 {
     Compiler compiler(&m_engine, &m_target);

From 77f687e7fafe35266bccde12e9ffa08720a9fec7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 11 Dec 2024 18:31:50 +0100
Subject: [PATCH 126/259] Implement control_stop

---
 src/dev/blocks/controlblocks.cpp        | 33 ++++++++++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 82 +++++++++++++++++++++++++
 3 files changed, 116 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 288388e5..5a3bbb03 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -5,6 +5,9 @@
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/value.h>
 #include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
+#include <scratchcpp/dev/executioncontext.h>
+#include <scratchcpp/thread.h>
 
 #include "controlblocks.h"
 
@@ -26,6 +29,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_repeat", &compileRepeat);
     engine->addCompileFunction(this, "control_if", &compileIf);
     engine->addCompileFunction(this, "control_if_else", &compileIfElse);
+    engine->addCompileFunction(this, "control_stop", &compileStop);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -57,3 +61,32 @@ CompilerValue *ControlBlocks::compileIfElse(Compiler *compiler)
     compiler->moveToIfElse(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr, substack2 ? substack2->valueBlock() : nullptr);
     return nullptr;
 }
+
+CompilerValue *ControlBlocks::compileStop(Compiler *compiler)
+{
+    Field *option = compiler->field("STOP_OPTION");
+
+    if (option) {
+        std::string str = option->value().toString();
+
+        if (str == "all")
+            compiler->addFunctionCallWithCtx("control_stop_all", Compiler::StaticType::Void);
+        else if (str == "this script")
+            compiler->createStop();
+        else if (str == "other scripts in sprite" || str == "other scripts in stage")
+            compiler->addFunctionCallWithCtx("control_stop_other_scripts_in_target", Compiler::StaticType::Void);
+    }
+
+    return nullptr;
+}
+
+extern "C" void control_stop_all(ExecutionContext *ctx)
+{
+    ctx->engine()->stop();
+}
+
+extern "C" void control_stop_other_scripts_in_target(ExecutionContext *ctx)
+{
+    Thread *thread = ctx->thread();
+    ctx->engine()->stopTarget(thread->target(), thread);
+}
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index b58e7856..c2956a57 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -20,6 +20,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileRepeat(Compiler *compiler);
         static CompilerValue *compileIf(Compiler *compiler);
         static CompilerValue *compileIfElse(Compiler *compiler);
+        static CompilerValue *compileStop(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 73c9f4db..b98edfd3 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -4,6 +4,8 @@
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/input.h>
+#include <scratchcpp/script.h>
+#include <scratchcpp/thread.h>
 #include <enginemock.h>
 
 #include "../common.h"
@@ -250,3 +252,83 @@ TEST_F(ControlBlocksTest, IfElse)
         builder.run();
     }
 }
+
+TEST_F(ControlBlocksTest, Stop)
+{
+    auto target = std::make_shared<Sprite>();
+
+    // Stop all
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_stop");
+        builder.addDropdownField("STOP_OPTION", "all");
+        auto block = builder.currentBlock();
+
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, stop());
+        thread.run();
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    // Stop this script
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_stop");
+        builder.addDropdownField("STOP_OPTION", "this script");
+        builder.addBlock("test_print_test");
+
+        builder.build();
+
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_TRUE(testing::internal::GetCapturedStdout().empty());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    // Stop other scripts in sprite
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_stop");
+        builder.addDropdownField("STOP_OPTION", "other scripts in sprite");
+        auto block = builder.currentBlock();
+
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, stopTarget(target.get(), &thread));
+        thread.run();
+    }
+
+    // Stop other scripts in stage
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_stop");
+        builder.addDropdownField("STOP_OPTION", "other scripts in stage");
+        auto block = builder.currentBlock();
+
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, stopTarget(target.get(), &thread));
+        thread.run();
+    }
+}

From 21df2fe5456ca6329a4f4fb236553eb9677eb6a0 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 12 Dec 2024 22:58:58 +0100
Subject: [PATCH 127/259] Add StackTimer

---
 CMakeLists.txt                     |  1 +
 include/scratchcpp/istacktimer.h   | 33 +++++++++++++++++++++
 src/engine/CMakeLists.txt          |  2 ++
 src/engine/internal/stacktimer.cpp | 45 +++++++++++++++++++++++++++++
 src/engine/internal/stacktimer.h   | 33 +++++++++++++++++++++
 test/mocks/stacktimermock.h        | 16 +++++++++++
 test/timer/CMakeLists.txt          |  1 +
 test/timer/stacktimer_test.cpp     | 46 ++++++++++++++++++++++++++++++
 8 files changed, 177 insertions(+)
 create mode 100644 include/scratchcpp/istacktimer.h
 create mode 100644 src/engine/internal/stacktimer.cpp
 create mode 100644 src/engine/internal/stacktimer.h
 create mode 100644 test/mocks/stacktimermock.h
 create mode 100644 test/timer/stacktimer_test.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 27fc54d0..c08d26ae 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -59,6 +59,7 @@ target_sources(scratchcpp
     include/scratchcpp/sprite.h
     include/scratchcpp/textbubble.h
     include/scratchcpp/itimer.h
+    include/scratchcpp/istacktimer.h
     include/scratchcpp/keyevent.h
     include/scratchcpp/rect.h
     include/scratchcpp/igraphicseffect.h
diff --git a/include/scratchcpp/istacktimer.h b/include/scratchcpp/istacktimer.h
new file mode 100644
index 00000000..c482042b
--- /dev/null
+++ b/include/scratchcpp/istacktimer.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "global.h"
+
+namespace libscratchcpp
+{
+
+/*!
+ * \brief The IStackTimer interface represents a timer that can be used by blocks.
+ *
+ * You can get a stack timer using ExecutionContext#stackTimer().
+ */
+class LIBSCRATCHCPP_EXPORT IStackTimer
+{
+    public:
+        virtual ~IStackTimer() { }
+
+        /*! Starts the timer. */
+        virtual void start(double seconds) = 0;
+
+        /*! Stops the timer. */
+        virtual void stop() = 0;
+
+        /*! Returns true if the timer has been stopped using stop() or wasn't used at all. */
+        virtual bool stopped() const = 0;
+
+        /*! Returns true if the timer has elapsed. */
+        virtual bool elapsed() const = 0;
+};
+
+} // namespace libscratchcpp
diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt
index 71402cef..506489de 100644
--- a/src/engine/CMakeLists.txt
+++ b/src/engine/CMakeLists.txt
@@ -16,6 +16,8 @@ target_sources(scratchcpp
     internal/iclock.h
     internal/timer.cpp
     internal/timer.h
+    internal/stacktimer.cpp
+    internal/stacktimer.h
     internal/randomgenerator.h
     internal/randomgenerator.cpp
     internal/irandomgenerator.h
diff --git a/src/engine/internal/stacktimer.cpp b/src/engine/internal/stacktimer.cpp
new file mode 100644
index 00000000..01ae3bc7
--- /dev/null
+++ b/src/engine/internal/stacktimer.cpp
@@ -0,0 +1,45 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <cassert>
+#include <iostream>
+
+#include "stacktimer.h"
+#include "clock.h"
+
+using namespace libscratchcpp;
+
+StackTimer::StackTimer()
+{
+    m_clock = Clock::instance().get();
+}
+
+StackTimer::StackTimer(IClock *clock) :
+    m_clock(clock)
+{
+    assert(clock);
+}
+
+void StackTimer::start(double seconds)
+{
+    m_startTime = m_clock->currentSteadyTime();
+    m_timeLimit = seconds * 1000;
+    m_stopped = false;
+}
+
+void StackTimer::stop()
+{
+    m_stopped = true;
+}
+
+bool StackTimer::stopped() const
+{
+    return m_stopped;
+}
+
+bool StackTimer::elapsed() const
+{
+    if (m_stopped)
+        return false;
+
+    return std::chrono::duration_cast<std::chrono::milliseconds>(m_clock->currentSteadyTime() - m_startTime).count() >= m_timeLimit;
+}
diff --git a/src/engine/internal/stacktimer.h b/src/engine/internal/stacktimer.h
new file mode 100644
index 00000000..54ece0c8
--- /dev/null
+++ b/src/engine/internal/stacktimer.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/istacktimer.h>
+#include <chrono>
+
+namespace libscratchcpp
+{
+
+class IClock;
+
+class StackTimer : public IStackTimer
+{
+    public:
+        StackTimer();
+        StackTimer(IClock *clock);
+        StackTimer(const StackTimer &) = delete;
+
+        void start(double seconds) override;
+        void stop() override;
+
+        bool stopped() const override;
+        bool elapsed() const override;
+
+    private:
+        std::chrono::steady_clock::time_point m_startTime;
+        bool m_stopped = true;
+        long m_timeLimit = 0;
+        IClock *m_clock = nullptr;
+};
+
+} // namespace libscratchcpp
diff --git a/test/mocks/stacktimermock.h b/test/mocks/stacktimermock.h
new file mode 100644
index 00000000..9a614d3e
--- /dev/null
+++ b/test/mocks/stacktimermock.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <scratchcpp/istacktimer.h>
+#include <gmock/gmock.h>
+
+using namespace libscratchcpp;
+
+class StackTimerMock : public IStackTimer
+{
+    public:
+        MOCK_METHOD(void, start, (double), (override));
+        MOCK_METHOD(void, stop, (), (override));
+
+        MOCK_METHOD(bool, stopped, (), (const, override));
+        MOCK_METHOD(bool, elapsed, (), (const, override));
+};
diff --git a/test/timer/CMakeLists.txt b/test/timer/CMakeLists.txt
index 5041f24e..3157ae45 100644
--- a/test/timer/CMakeLists.txt
+++ b/test/timer/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_executable(
   timer_test
   timer_test.cpp
+  stacktimer_test.cpp
 )
 
 target_link_libraries(
diff --git a/test/timer/stacktimer_test.cpp b/test/timer/stacktimer_test.cpp
new file mode 100644
index 00000000..6d06da90
--- /dev/null
+++ b/test/timer/stacktimer_test.cpp
@@ -0,0 +1,46 @@
+#include <engine/internal/stacktimer.h>
+
+#include <clockmock.h>
+
+using namespace libscratchcpp;
+
+using ::testing::Return;
+
+TEST(StackTimerTest, StartStopElapsed)
+{
+    ClockMock clock;
+    StackTimer timer(&clock);
+
+    ASSERT_TRUE(timer.stopped());
+
+    EXPECT_CALL(clock, currentSteadyTime).Times(0);
+    ASSERT_FALSE(timer.elapsed());
+
+    std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(73));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2));
+    timer.start(0.5);
+
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2));
+    ASSERT_FALSE(timer.elapsed());
+    ASSERT_FALSE(timer.stopped());
+
+    std::chrono::steady_clock::time_point time3(std::chrono::milliseconds(520));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time3));
+    ASSERT_FALSE(timer.elapsed());
+    ASSERT_FALSE(timer.stopped());
+
+    std::chrono::steady_clock::time_point time4(std::chrono::milliseconds(573));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time4));
+    ASSERT_TRUE(timer.elapsed());
+    ASSERT_FALSE(timer.stopped());
+
+    std::chrono::steady_clock::time_point time5(std::chrono::milliseconds(580));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time5));
+    ASSERT_TRUE(timer.elapsed());
+    ASSERT_FALSE(timer.stopped());
+
+    timer.stop();
+    EXPECT_CALL(clock, currentSteadyTime).Times(0);
+    ASSERT_FALSE(timer.elapsed());
+    ASSERT_TRUE(timer.stopped());
+}

From 5744b3dd8413a2d349961219ac4806b762b60985 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 12 Dec 2024 23:03:10 +0100
Subject: [PATCH 128/259] ExecutionContext: Add stack timer

---
 include/scratchcpp/dev/executioncontext.h           |  4 ++++
 src/dev/engine/executioncontext.cpp                 | 12 ++++++++++++
 src/dev/engine/executioncontext_p.cpp               |  4 +++-
 src/dev/engine/executioncontext_p.h                 |  5 ++++-
 test/dev/executioncontext/executioncontext_test.cpp | 11 +++++++++++
 5 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h
index 2f41ea65..970041c0 100644
--- a/include/scratchcpp/dev/executioncontext.h
+++ b/include/scratchcpp/dev/executioncontext.h
@@ -11,6 +11,7 @@ namespace libscratchcpp
 class Thread;
 class IEngine;
 class Promise;
+class IStackTimer;
 class ExecutionContextPrivate;
 
 /*! \brief The ExecutionContext represents the execution context of a target (can be a clone) with variables, lists, etc. */
@@ -27,6 +28,9 @@ class LIBSCRATCHCPP_EXPORT ExecutionContext
         std::shared_ptr<Promise> promise() const;
         void setPromise(std::shared_ptr<Promise> promise);
 
+        IStackTimer *stackTimer() const;
+        void setStackTimer(IStackTimer *newStackTimer);
+
     private:
         spimpl::unique_impl_ptr<ExecutionContextPrivate> impl;
 };
diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp
index b0e74926..5c72cd54 100644
--- a/src/dev/engine/executioncontext.cpp
+++ b/src/dev/engine/executioncontext.cpp
@@ -36,3 +36,15 @@ void ExecutionContext::setPromise(std::shared_ptr<Promise> promise)
 {
     impl->promise = promise;
 }
+
+/*! Returns the stack timer of this context. Can be used for wait blocks. */
+IStackTimer *ExecutionContext::stackTimer() const
+{
+    return impl->stackTimer;
+}
+
+/*! Sets a custom stack timer. */
+void ExecutionContext::setStackTimer(IStackTimer *newStackTimer)
+{
+    impl->stackTimer = newStackTimer;
+}
diff --git a/src/dev/engine/executioncontext_p.cpp b/src/dev/engine/executioncontext_p.cpp
index bb42caac..03970aa7 100644
--- a/src/dev/engine/executioncontext_p.cpp
+++ b/src/dev/engine/executioncontext_p.cpp
@@ -5,6 +5,8 @@
 using namespace libscratchcpp;
 
 ExecutionContextPrivate::ExecutionContextPrivate(Thread *thread) :
-    thread(thread)
+    thread(thread),
+    defaultStackTimer(std::make_unique<StackTimer>()),
+    stackTimer(defaultStackTimer.get())
 {
 }
diff --git a/src/dev/engine/executioncontext_p.h b/src/dev/engine/executioncontext_p.h
index 19322c13..c556839d 100644
--- a/src/dev/engine/executioncontext_p.h
+++ b/src/dev/engine/executioncontext_p.h
@@ -4,11 +4,12 @@
 
 #include <memory>
 
+#include "../../engine/internal/stacktimer.h"
+
 namespace libscratchcpp
 {
 
 class Thread;
-class Target;
 class Promise;
 
 struct ExecutionContextPrivate
@@ -17,6 +18,8 @@ struct ExecutionContextPrivate
 
         Thread *thread = nullptr;
         std::shared_ptr<Promise> promise;
+        std::unique_ptr<IStackTimer> defaultStackTimer;
+        IStackTimer *stackTimer = nullptr;
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/executioncontext/executioncontext_test.cpp b/test/dev/executioncontext/executioncontext_test.cpp
index 38122f6f..48504cd1 100644
--- a/test/dev/executioncontext/executioncontext_test.cpp
+++ b/test/dev/executioncontext/executioncontext_test.cpp
@@ -3,6 +3,7 @@
 #include <scratchcpp/dev/promise.h>
 
 #include <enginemock.h>
+#include <stacktimermock.h>
 
 #include "../../common.h"
 
@@ -29,3 +30,13 @@ TEST(ExecutionContextTest, Promise)
     ctx.setPromise(nullptr);
     ASSERT_EQ(ctx.promise(), nullptr);
 }
+
+TEST(ExecutionContextTest, StackTimer)
+{
+    ExecutionContext ctx(nullptr);
+    ASSERT_TRUE(ctx.stackTimer());
+
+    StackTimerMock timer;
+    ctx.setStackTimer(&timer);
+    ASSERT_EQ(ctx.stackTimer(), &timer);
+}

From f6ee5581b8298dbadded17d5096bb6f88132438f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 12 Dec 2024 23:35:12 +0100
Subject: [PATCH 129/259] Compiler: Add createYield() method

---
 include/scratchcpp/dev/compiler.h   |  1 +
 src/dev/engine/compiler.cpp         |  6 ++++++
 test/dev/compiler/compiler_test.cpp | 15 ++++++++++++++-
 3 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index ec326608..94d9c5da 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -115,6 +115,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block> substack);
         void warp();
 
+        void createYield();
         void createStop();
 
         Input *input(const std::string &name) const;
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 5bdeacf7..0ed85e99 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -495,6 +495,12 @@ void Compiler::warp()
     impl->warp = true;
 }
 
+/*! Creates a suspend instruction. */
+void Compiler::createYield()
+{
+    impl->builder->yield();
+}
+
 /*! Creates a stop script instruction. */
 void Compiler::createStop()
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 70ab740d..3e68d9af 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -1418,13 +1418,26 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop)
     compile(compiler, l1);
 }
 
+TEST_F(CompilerTest, CreateYield)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        EXPECT_CALL(*m_builder, yield());
+        compiler->createYield();
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, CreateStop)
 {
     Compiler compiler(&m_engine, &m_target);
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
-        CompilerValue arg(Compiler::StaticType::Unknown);
         EXPECT_CALL(*m_builder, createStop());
         compiler->createStop();
         return nullptr;

From 5a672685b818562cedee5e8c5057d5a1ce50b871 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 12 Dec 2024 23:35:53 +0100
Subject: [PATCH 130/259] Implement control_wait

---
 src/dev/blocks/controlblocks.cpp        | 26 +++++++++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 72 +++++++++++++++++++++++++
 3 files changed, 99 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 5a3bbb03..4f2bc8dd 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -8,6 +8,7 @@
 #include <scratchcpp/field.h>
 #include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/thread.h>
+#include <scratchcpp/istacktimer.h>
 
 #include "controlblocks.h"
 
@@ -30,6 +31,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_if", &compileIf);
     engine->addCompileFunction(this, "control_if_else", &compileIfElse);
     engine->addCompileFunction(this, "control_stop", &compileStop);
+    engine->addCompileFunction(this, "control_wait", &compileWait);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -80,6 +82,20 @@ CompilerValue *ControlBlocks::compileStop(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileWait(Compiler *compiler)
+{
+    auto duration = compiler->addInput("DURATION");
+    compiler->addFunctionCallWithCtx("control_start_stack_timer", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration });
+    compiler->createYield();
+
+    compiler->beginLoopCondition();
+    auto elapsed = compiler->addFunctionCallWithCtx("control_stack_timer_elapsed", Compiler::StaticType::Bool);
+    compiler->beginRepeatUntilLoop(elapsed);
+    compiler->endLoop();
+
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
@@ -90,3 +106,13 @@ extern "C" void control_stop_other_scripts_in_target(ExecutionContext *ctx)
     Thread *thread = ctx->thread();
     ctx->engine()->stopTarget(thread->target(), thread);
 }
+
+extern "C" void control_start_stack_timer(ExecutionContext *ctx, double seconds)
+{
+    ctx->stackTimer()->start(seconds);
+}
+
+extern "C" bool control_stack_timer_elapsed(ExecutionContext *ctx)
+{
+    return ctx->stackTimer()->elapsed();
+}
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index c2956a57..00908872 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -21,6 +21,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileIf(Compiler *compiler);
         static CompilerValue *compileIfElse(Compiler *compiler);
         static CompilerValue *compileStop(Compiler *compiler);
+        static CompilerValue *compileWait(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index b98edfd3..e5eb425f 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -6,7 +6,11 @@
 #include <scratchcpp/input.h>
 #include <scratchcpp/script.h>
 #include <scratchcpp/thread.h>
+#include <scratchcpp/dev/executablecode.h>
+#include <scratchcpp/dev/executioncontext.h>
+
 #include <enginemock.h>
+#include <stacktimermock.h>
 
 #include "../common.h"
 #include "dev/blocks/controlblocks.h"
@@ -15,6 +19,8 @@
 using namespace libscratchcpp;
 using namespace libscratchcpp::test;
 
+using ::testing::Return;
+
 class ControlBlocksTest : public testing::Test
 {
     public:
@@ -332,3 +338,69 @@ TEST_F(ControlBlocksTest, Stop)
         thread.run();
     }
 }
+
+TEST_F(ControlBlocksTest, Wait)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_wait");
+        builder.addValueInput("DURATION", 2.5);
+        auto block = builder.currentBlock();
+
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+        auto ctx = code->createExecutionContext(&thread);
+        StackTimerMock timer;
+        ctx->setStackTimer(&timer);
+
+        EXPECT_CALL(timer, start(2.5));
+        code->run(ctx.get());
+        ASSERT_FALSE(code->isFinished(ctx.get()));
+
+        EXPECT_CALL(timer, elapsed()).WillOnce(Return(false));
+        code->run(ctx.get());
+        ASSERT_FALSE(code->isFinished(ctx.get()));
+
+        EXPECT_CALL(timer, elapsed()).WillOnce(Return(false));
+        code->run(ctx.get());
+        ASSERT_FALSE(code->isFinished(ctx.get()));
+
+        EXPECT_CALL(timer, elapsed()).WillOnce(Return(true));
+        code->run(ctx.get());
+        ASSERT_TRUE(code->isFinished(ctx.get()));
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_wait");
+        builder.addNullObscuredInput("DURATION");
+        auto block = builder.currentBlock();
+
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+        auto ctx = code->createExecutionContext(&thread);
+        StackTimerMock timer;
+        ctx->setStackTimer(&timer);
+
+        EXPECT_CALL(timer, start(0.0));
+        code->run(ctx.get());
+        ASSERT_FALSE(code->isFinished(ctx.get()));
+
+        EXPECT_CALL(timer, elapsed()).WillOnce(Return(true));
+        code->run(ctx.get());
+        ASSERT_TRUE(code->isFinished(ctx.get()));
+    }
+}

From 880e30ed713910d2e2b9ad27ad70b29374ecff7f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 12:45:44 +0100
Subject: [PATCH 131/259] Request redraw in wait block

---
 src/dev/blocks/controlblocks.cpp        | 5 +++--
 test/dev/blocks/control_blocks_test.cpp | 6 ++++++
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 4f2bc8dd..3fd04962 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -85,7 +85,7 @@ CompilerValue *ControlBlocks::compileStop(Compiler *compiler)
 CompilerValue *ControlBlocks::compileWait(Compiler *compiler)
 {
     auto duration = compiler->addInput("DURATION");
-    compiler->addFunctionCallWithCtx("control_start_stack_timer", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration });
+    compiler->addFunctionCallWithCtx("control_start_wait", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration });
     compiler->createYield();
 
     compiler->beginLoopCondition();
@@ -107,9 +107,10 @@ extern "C" void control_stop_other_scripts_in_target(ExecutionContext *ctx)
     ctx->engine()->stopTarget(thread->target(), thread);
 }
 
-extern "C" void control_start_stack_timer(ExecutionContext *ctx, double seconds)
+extern "C" void control_start_wait(ExecutionContext *ctx, double seconds)
 {
     ctx->stackTimer()->start(seconds);
+    ctx->engine()->requestRedraw();
 }
 
 extern "C" bool control_stack_timer_elapsed(ExecutionContext *ctx)
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index e5eb425f..3f423213 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -360,18 +360,22 @@ TEST_F(ControlBlocksTest, Wait)
         ctx->setStackTimer(&timer);
 
         EXPECT_CALL(timer, start(2.5));
+        EXPECT_CALL(m_engineMock, requestRedraw());
         code->run(ctx.get());
         ASSERT_FALSE(code->isFinished(ctx.get()));
 
         EXPECT_CALL(timer, elapsed()).WillOnce(Return(false));
+        EXPECT_CALL(m_engineMock, requestRedraw()).Times(0);
         code->run(ctx.get());
         ASSERT_FALSE(code->isFinished(ctx.get()));
 
         EXPECT_CALL(timer, elapsed()).WillOnce(Return(false));
+        EXPECT_CALL(m_engineMock, requestRedraw()).Times(0);
         code->run(ctx.get());
         ASSERT_FALSE(code->isFinished(ctx.get()));
 
         EXPECT_CALL(timer, elapsed()).WillOnce(Return(true));
+        EXPECT_CALL(m_engineMock, requestRedraw()).Times(0);
         code->run(ctx.get());
         ASSERT_TRUE(code->isFinished(ctx.get()));
     }
@@ -396,10 +400,12 @@ TEST_F(ControlBlocksTest, Wait)
         ctx->setStackTimer(&timer);
 
         EXPECT_CALL(timer, start(0.0));
+        EXPECT_CALL(m_engineMock, requestRedraw());
         code->run(ctx.get());
         ASSERT_FALSE(code->isFinished(ctx.get()));
 
         EXPECT_CALL(timer, elapsed()).WillOnce(Return(true));
+        EXPECT_CALL(m_engineMock, requestRedraw()).Times(0);
         code->run(ctx.get());
         ASSERT_TRUE(code->isFinished(ctx.get()));
     }

From 90b566c327e46b29bda5b51f2e727dacb6523ffa Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 13:11:30 +0100
Subject: [PATCH 132/259] Implement control_wait_until

---
 src/dev/blocks/controlblocks.cpp        |  9 ++++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 62 +++++++++++++++++++++++++
 test/dev/blocks/util.cpp                |  7 +++
 test/dev/blocks/util.h                  |  2 +
 5 files changed, 81 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 3fd04962..54f95d9a 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -32,6 +32,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_if_else", &compileIfElse);
     engine->addCompileFunction(this, "control_stop", &compileStop);
     engine->addCompileFunction(this, "control_wait", &compileWait);
+    engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -96,6 +97,14 @@ CompilerValue *ControlBlocks::compileWait(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileWaitUntil(Compiler *compiler)
+{
+    compiler->beginLoopCondition();
+    compiler->beginRepeatUntilLoop(compiler->addInput("CONDITION"));
+    compiler->endLoop();
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 00908872..9d1e72e3 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -22,6 +22,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileIfElse(Compiler *compiler);
         static CompilerValue *compileStop(Compiler *compiler);
         static CompilerValue *compileWait(Compiler *compiler);
+        static CompilerValue *compileWaitUntil(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 3f423213..ac85410f 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -410,3 +410,65 @@ TEST_F(ControlBlocksTest, Wait)
         ASSERT_TRUE(code->isFinished(ctx.get()));
     }
 }
+
+TEST_F(ControlBlocksTest, WaitUntil)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_wait_until");
+        builder.addValueInput("CONDITION", false);
+        builder.build();
+        m_engine->start();
+
+        m_engine->step();
+        ASSERT_TRUE(m_engine->isRunning());
+
+        m_engine->step();
+        ASSERT_TRUE(m_engine->isRunning());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_wait_until");
+        builder.addValueInput("CONDITION", true);
+        builder.build();
+        m_engine->start();
+
+        m_engine->step();
+        m_engine->step();
+        ASSERT_FALSE(m_engine->isRunning());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_wait_until");
+        auto block = std::make_shared<Block>("", "test_condition");
+        builder.addObscuredInput("CONDITION", block);
+        builder.build();
+
+        conditionReturnValue = false;
+        m_engine->start();
+
+        m_engine->step();
+        ASSERT_TRUE(m_engine->isRunning());
+
+        m_engine->step();
+        ASSERT_TRUE(m_engine->isRunning());
+
+        conditionReturnValue = true;
+        m_engine->step();
+        m_engine->step();
+        ASSERT_FALSE(m_engine->isRunning());
+    }
+}
diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp
index 4cc5d279..a6890119 100644
--- a/test/dev/blocks/util.cpp
+++ b/test/dev/blocks/util.cpp
@@ -28,6 +28,8 @@ void registerBlocks(IEngine *engine, IExtension *extension)
         compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input });
         return nullptr;
     });
+
+    engine->addCompileFunction(extension, "test_condition", [](Compiler *compiler) -> CompilerValue * { return compiler->addFunctionCall("test_condition", Compiler::StaticType::Bool); });
 }
 
 extern "C" void test_print(const char *str)
@@ -35,4 +37,9 @@ extern "C" void test_print(const char *str)
     std::cout << str << std::endl;
 }
 
+extern "C" bool test_condition()
+{
+    return conditionReturnValue;
+}
+
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/util.h b/test/dev/blocks/util.h
index c82c34a4..9fc7e76f 100644
--- a/test/dev/blocks/util.h
+++ b/test/dev/blocks/util.h
@@ -6,6 +6,8 @@ namespace libscratchcpp
 class IEngine;
 class IExtension;
 
+bool conditionReturnValue = false;
+
 void registerBlocks(IEngine *engine, IExtension *extension);
 
 } // namespace libscratchcpp

From 834b91c72ac995c8191deae16ffb314a9a8af726 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 14:31:44 +0100
Subject: [PATCH 133/259] Implement control_repeat_until

---
 src/dev/blocks/controlblocks.cpp        |  9 +++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 87 +++++++++++++++++++++++++
 3 files changed, 97 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 54f95d9a..41e8d24f 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -33,6 +33,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_stop", &compileStop);
     engine->addCompileFunction(this, "control_wait", &compileWait);
     engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
+    engine->addCompileFunction(this, "control_repeat_until", &compileRepeatUntil);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -105,6 +106,14 @@ CompilerValue *ControlBlocks::compileWaitUntil(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileRepeatUntil(Compiler *compiler)
+{
+    auto substack = compiler->input("SUBSTACK");
+    compiler->beginLoopCondition();
+    compiler->moveToRepeatUntilLoop(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr);
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 9d1e72e3..e9f902cc 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -23,6 +23,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileStop(Compiler *compiler);
         static CompilerValue *compileWait(Compiler *compiler);
         static CompilerValue *compileWaitUntil(Compiler *compiler);
+        static CompilerValue *compileRepeatUntil(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index ac85410f..91aa04f3 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -472,3 +472,90 @@ TEST_F(ControlBlocksTest, WaitUntil)
         ASSERT_FALSE(m_engine->isRunning());
     }
 }
+
+TEST_F(ControlBlocksTest, RepeatUntil)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_repeat_until");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        builder.addValueInput("CONDITION", false);
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            testing::internal::CaptureStdout();
+            m_engine->step();
+            ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n");
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_repeat_until");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        builder.addValueInput("CONDITION", true);
+        builder.build();
+        m_engine->start();
+
+        testing::internal::CaptureStdout();
+        m_engine->step();
+        m_engine->step();
+        ASSERT_TRUE(testing::internal::GetCapturedStdout().empty());
+        ASSERT_FALSE(m_engine->isRunning());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_repeat_until");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        auto block = std::make_shared<Block>("", "test_condition");
+        builder.addObscuredInput("CONDITION", block);
+        builder.build();
+
+        conditionReturnValue = false;
+        m_engine->start();
+
+        testing::internal::CaptureStdout();
+        m_engine->step();
+        ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n");
+        ASSERT_TRUE(m_engine->isRunning());
+
+        conditionReturnValue = true;
+        m_engine->step();
+        m_engine->step();
+        ASSERT_FALSE(m_engine->isRunning());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+        builder.addBlock("control_repeat_until");
+        builder.addValueInput("CONDITION", false);
+
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            m_engine->step();
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+    }
+}

From f62c2e0770a523dd5f12c0b3cc91493cced1cc20 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 14:39:38 +0100
Subject: [PATCH 134/259] Implement control_while

---
 src/dev/blocks/controlblocks.cpp        |  9 +++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 87 +++++++++++++++++++++++++
 3 files changed, 97 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 41e8d24f..411940e4 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -34,6 +34,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_wait", &compileWait);
     engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
     engine->addCompileFunction(this, "control_repeat_until", &compileRepeatUntil);
+    engine->addCompileFunction(this, "control_while", &compileWhile);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -114,6 +115,14 @@ CompilerValue *ControlBlocks::compileRepeatUntil(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileWhile(Compiler *compiler)
+{
+    auto substack = compiler->input("SUBSTACK");
+    compiler->beginLoopCondition();
+    compiler->moveToWhileLoop(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr);
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index e9f902cc..d2722809 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -24,6 +24,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileWait(Compiler *compiler);
         static CompilerValue *compileWaitUntil(Compiler *compiler);
         static CompilerValue *compileRepeatUntil(Compiler *compiler);
+        static CompilerValue *compileWhile(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 91aa04f3..19215797 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -559,3 +559,90 @@ TEST_F(ControlBlocksTest, RepeatUntil)
         }
     }
 }
+
+TEST_F(ControlBlocksTest, While)
+{
+    auto target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_while");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        builder.addValueInput("CONDITION", true);
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            testing::internal::CaptureStdout();
+            m_engine->step();
+            ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n");
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_while");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        builder.addValueInput("CONDITION", false);
+        builder.build();
+        m_engine->start();
+
+        testing::internal::CaptureStdout();
+        m_engine->step();
+        m_engine->step();
+        ASSERT_TRUE(testing::internal::GetCapturedStdout().empty());
+        ASSERT_FALSE(m_engine->isRunning());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_while");
+        auto substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        auto block = std::make_shared<Block>("", "test_condition");
+        builder.addObscuredInput("CONDITION", block);
+        builder.build();
+
+        conditionReturnValue = true;
+        m_engine->start();
+
+        testing::internal::CaptureStdout();
+        m_engine->step();
+        ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n");
+        ASSERT_TRUE(m_engine->isRunning());
+
+        conditionReturnValue = false;
+        m_engine->step();
+        m_engine->step();
+        ASSERT_FALSE(m_engine->isRunning());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+        builder.addBlock("control_while");
+        builder.addValueInput("CONDITION", true);
+
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            m_engine->step();
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+    }
+}

From b389f56d71c41b6e3eb92d899910d814cc5464e8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:10:08 +0100
Subject: [PATCH 135/259] LLVMCodeBuilder: Use unsigned integer in repeat loop

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index eac2f868..5c4b8c64 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -796,7 +796,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.SetInsertPoint(roundBranch);
                 llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, { count->getType() });
                 count = m_builder.CreateCall(roundFunc, { count });
-                count = m_builder.CreateFPToSI(count, m_builder.getInt64Ty()); // cast to signed integer
+                count = m_builder.CreateFPToUI(count, m_builder.getInt64Ty()); // cast to unsigned integer
                 count = m_builder.CreateSelect(isInf, zero, count);
 
                 // Jump to condition branch
@@ -884,7 +884,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 if (loop.isRepeatLoop) {
                     // Increment index
                     llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index);
-                    llvm::Value *incremented = m_builder.CreateAdd(currentIndex, llvm::ConstantInt::get(m_builder.getInt64Ty(), 1, true));
+                    llvm::Value *incremented = m_builder.CreateAdd(currentIndex, llvm::ConstantInt::get(m_builder.getInt64Ty(), 1, false));
                     m_builder.CreateStore(incremented, loop.index);
                 }
 

From f274cf65a023e5cf94cead2e69517c5a830510c9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:10:55 +0100
Subject: [PATCH 136/259] LLVMCodeBuilder: Add addLoopIndex() method

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 13 +++++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 +
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 23 ++++++++++---------
 test/mocks/codebuildermock.h                  |  1 +
 6 files changed, 29 insertions(+), 11 deletions(-)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 3b1f5419..c92c3222 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -23,6 +23,7 @@ class ICodeBuilder
         virtual CompilerValue *addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0;
         virtual CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0;
         virtual CompilerConstant *addConstValue(const Value &value) = 0;
+        virtual CompilerValue *addLoopIndex() = 0;
         virtual CompilerValue *addVariableValue(Variable *variable) = 0;
         virtual CompilerValue *addListContents(List *list) = 0;
         virtual CompilerValue *addListItem(List *list, CompilerValue *index) = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 5c4b8c64..069f7bd9 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -822,6 +822,14 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::LoopIndex: {
+                assert(!loops.empty());
+                LLVMLoop &loop = loops.back();
+                llvm::Value *index = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index);
+                step.functionReturnReg->value = m_builder.CreateUIToFP(index, m_builder.getDoubleTy());
+                break;
+            }
+
             case LLVMInstruction::Type::BeginWhileLoop: {
                 assert(!loops.empty());
                 LLVMLoop &loop = loops.back();
@@ -993,6 +1001,11 @@ CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value)
     return static_cast<CompilerConstant *>(addReg(reg));
 }
 
+CompilerValue *LLVMCodeBuilder::addLoopIndex()
+{
+    return createOp(LLVMInstruction::Type::LoopIndex, Compiler::StaticType::Number, {}, {});
+}
+
 CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
     LLVMInstruction ins(LLVMInstruction::Type::ReadVariable);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 3f96bc0a..afdf12de 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -30,6 +30,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         CompilerValue *addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override;
         CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override;
         CompilerConstant *addConstValue(const Value &value) override;
+        CompilerValue *addLoopIndex() override;
         CompilerValue *addVariableValue(Variable *variable) override;
         CompilerValue *addListContents(List *list) override;
         CompilerValue *addListItem(List *list, CompilerValue *index) override;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index a016365b..9b81b563 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -58,6 +58,7 @@ struct LLVMInstruction
             BeginElse,
             EndIf,
             BeginRepeatLoop,
+            LoopIndex,
             BeginWhileLoop,
             BeginRepeatUntilLoop,
             BeginLoopCondition,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 02bb877d..6fd62ac1 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2907,7 +2907,8 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     v = m_builder->addConstValue(2);
     v = callConstFuncForType(ValueType::Number, v);
     m_builder->beginRepeatLoop(v);
-    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    CompilerValue *index = m_builder->addLoopIndex();
+    m_builder->addTargetFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { index });
     m_builder->endLoop();
 
     // Nested
@@ -2928,8 +2929,8 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
         v = m_builder->addConstValue(3);
         m_builder->beginRepeatLoop(v);
         {
-            v = m_builder->addConstValue(3);
-            m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+            index = m_builder->addLoopIndex();
+            m_builder->addTargetFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { index });
         }
         m_builder->endLoop();
     }
@@ -2950,20 +2951,20 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
         "1_arg 1\n"
         "1_arg 1\n"
         "1_arg 1\n"
-        "no_args\n"
-        "no_args\n"
+        "0\n"
+        "1\n"
         "1_arg 1\n"
         "1_arg 1\n"
         "1_arg 2\n"
-        "1_arg 3\n"
-        "1_arg 3\n"
-        "1_arg 3\n"
+        "0\n"
+        "1\n"
+        "2\n"
         "1_arg 1\n"
         "1_arg 1\n"
         "1_arg 2\n"
-        "1_arg 3\n"
-        "1_arg 3\n"
-        "1_arg 3\n";
+        "0\n"
+        "1\n"
+        "2\n";
 
     EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false));
     testing::internal::CaptureStdout();
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index f66408e7..478252d6 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -13,6 +13,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(CompilerValue *, addTargetFunctionCall, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override));
         MOCK_METHOD(CompilerValue *, addFunctionCallWithCtx, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override));
         MOCK_METHOD(CompilerConstant *, addConstValue, (const Value &), (override));
+        MOCK_METHOD(CompilerValue *, addLoopIndex, (), (override));
         MOCK_METHOD(CompilerValue *, addVariableValue, (Variable *), (override));
         MOCK_METHOD(CompilerValue *, addListContents, (List *), (override));
         MOCK_METHOD(CompilerValue *, addListItem, (List *, CompilerValue *), (override));

From cc2aca906bdeaa598de86e57c97037a4cc8a39c9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:56:27 +0100
Subject: [PATCH 137/259] Compiler: Add addLoopIndex() method

---
 include/scratchcpp/dev/compiler.h   |  1 +
 src/dev/engine/compiler.cpp         |  6 ++++++
 test/dev/compiler/compiler_test.cpp | 19 +++++++++++++++++++
 3 files changed, 26 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 94d9c5da..b7f5cc73 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -51,6 +51,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *addTargetFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
         CompilerValue *addFunctionCallWithCtx(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
         CompilerConstant *addConstValue(const Value &value);
+        CompilerValue *addLoopIndex();
         CompilerValue *addVariableValue(Variable *variable);
         CompilerValue *addListContents(List *list);
         CompilerValue *addListItem(List *list, CompilerValue *index);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 0ed85e99..772de15a 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -115,6 +115,12 @@ CompilerConstant *Compiler::addConstValue(const Value &value)
     return static_cast<CompilerConstant *>(impl->builder->addConstValue(value));
 }
 
+/*! Adds the index of the current repeat loop to the compiled code. */
+CompilerValue *Compiler::addLoopIndex()
+{
+    return impl->builder->addLoopIndex();
+}
+
 /*! Adds the value of the given variable to the code. */
 CompilerValue *Compiler::addVariableValue(Variable *variable)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 3e68d9af..cacd80b5 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -161,6 +161,25 @@ TEST_F(CompilerTest, AddConstValue)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, AddLoopIndex)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
+
+        EXPECT_CALL(*m_builder, addLoopIndex()).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addLoopIndex(), &ret);
+
+        EXPECT_CALL(*m_builder, addLoopIndex()).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addLoopIndex(), nullptr);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, AddVariableValue)
 {
     Compiler compiler(&m_engine, &m_target);

From 53e30e23a8f01b4c8aa417aed5d84971afd6bf5f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 16:57:08 +0100
Subject: [PATCH 138/259] Compiler: Allow custom code in empty substacks

---
 src/dev/engine/compiler.cpp | 14 ++++++++++----
 src/dev/engine/compiler_p.h |  1 +
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 772de15a..0c633c72 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -42,6 +42,7 @@ std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBl
     impl->builder = impl->builderFactory->create(impl->target, startBlock->id(), false);
     impl->substackTree.clear();
     impl->substackHit = false;
+    impl->emptySubstack = false;
     impl->warp = false;
     impl->block = startBlock;
 
@@ -55,6 +56,11 @@ std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBl
                 assert(false);
             }
 
+            if (impl->emptySubstack) {
+                impl->emptySubstack = false;
+                impl->substackEnd();
+            }
+
             if (impl->customLoopCount > 0) {
                 std::cerr << "error: loop created by block '" << impl->block->opcode() << "' not terminated" << std::endl;
                 assert(false);
@@ -456,7 +462,7 @@ void Compiler::moveToIfElse(CompilerValue *cond, std::shared_ptr<Block> substack
     impl->builder->beginIfStatement(cond);
 
     if (!impl->block)
-        impl->substackEnd();
+        impl->emptySubstack = true;
 }
 
 /*! Jumps to the given repeat loop substack. */
@@ -468,7 +474,7 @@ void Compiler::moveToRepeatLoop(CompilerValue *count, std::shared_ptr<Block> sub
     impl->builder->beginRepeatLoop(count);
 
     if (!impl->block)
-        impl->substackEnd();
+        impl->emptySubstack = true;
 }
 
 /*! Jumps to the given while loop substack. */
@@ -480,7 +486,7 @@ void Compiler::moveToWhileLoop(CompilerValue *cond, std::shared_ptr<Block> subst
     impl->builder->beginWhileLoop(cond);
 
     if (!impl->block)
-        impl->substackEnd();
+        impl->emptySubstack = true;
 }
 
 /*! Jumps to the given until loop substack. */
@@ -492,7 +498,7 @@ void Compiler::moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr<Block>
     impl->builder->beginRepeatUntilLoop(cond);
 
     if (!impl->block)
-        impl->substackEnd();
+        impl->emptySubstack = true;
 }
 
 /*! Makes current script run without screen refresh. */
diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h
index 740d3be2..add1021c 100644
--- a/src/dev/engine/compiler_p.h
+++ b/src/dev/engine/compiler_p.h
@@ -36,6 +36,7 @@ struct CompilerPrivate
         int customLoopCount = 0;
         std::vector<std::pair<std::pair<std::shared_ptr<Block>, std::shared_ptr<Block>>, SubstackType>> substackTree;
         bool substackHit = false;
+        bool emptySubstack = false;
         bool warp = false;
 
         static inline ICodeBuilderFactory *builderFactory = nullptr;

From 9e8440df123266b8f2dc512795f892c88e4dd868 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 18:05:19 +0100
Subject: [PATCH 139/259] ScriptBuilder: Add support for substack sequences

---
 src/dev/test/scriptbuilder.cpp           | 19 +++++++++++++++++--
 test/dev/test_api/scriptbuilder_test.cpp | 19 +++++++++++++++++++
 test/dev/test_api/testextension.cpp      |  8 ++++++++
 test/dev/test_api/testextension.h        |  1 +
 4 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index 97d3aac2..ea232b07 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -103,8 +103,23 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Bl
     if (!impl->lastBlock)
         return;
 
-    valueBlock->setId(std::to_string(impl->blockId++));
-    impl->inputBlocks.push_back(valueBlock);
+    auto block = valueBlock;
+
+    while (block) {
+        block->setId(std::to_string(impl->blockId++));
+        impl->inputBlocks.push_back(block);
+
+        auto parent = block->parent();
+        auto next = block->next();
+
+        if (parent)
+            parent->setNext(block);
+
+        if (next)
+            next->setParent(block);
+
+        block = next;
+    }
 
     auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
     input->setValueBlock(valueBlock);
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index a2d4eb0e..3191d63f 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -110,6 +110,25 @@ TEST_F(ScriptBuilderTest, AddObscuredInput)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
 }
 
+TEST_F(ScriptBuilderTest, AddObscuredInputMultipleBlocks)
+{
+    m_builder->addBlock("test_substack");
+    auto substack = std::make_shared<Block>("", "test_simple");
+    auto block1 = std::make_shared<Block>("", "test_simple");
+    substack->setNext(block1);
+    block1->setParent(substack);
+    auto block2 = std::make_shared<Block>("", "test_simple");
+    block1->setNext(block2);
+    block2->setParent(block1);
+    m_builder->addObscuredInput("SUBSTACK", substack);
+
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\ntest\ntest\n");
+}
+
 TEST_F(ScriptBuilderTest, AddNullObscuredInput)
 {
     m_builder->addBlock("test_print");
diff --git a/test/dev/test_api/testextension.cpp b/test/dev/test_api/testextension.cpp
index d24cbf3a..7e6ef22d 100644
--- a/test/dev/test_api/testextension.cpp
+++ b/test/dev/test_api/testextension.cpp
@@ -29,6 +29,7 @@ void TestExtension::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "test_print_field", &compilePrintField);
     engine->addCompileFunction(this, "test_teststr", &compileTestStr);
     engine->addCompileFunction(this, "test_input", &compileInput);
+    engine->addCompileFunction(this, "test_substack", &compileSubstack);
 }
 
 CompilerValue *TestExtension::compileSimple(Compiler *compiler)
@@ -69,6 +70,13 @@ CompilerValue *TestExtension::compileInput(Compiler *compiler)
     return compiler->addInput("INPUT");
 }
 
+CompilerValue *TestExtension::compileSubstack(Compiler *compiler)
+{
+    auto substack = compiler->input("SUBSTACK");
+    compiler->moveToIf(compiler->addConstValue(true), substack->valueBlock());
+    return nullptr;
+}
+
 extern "C" void test_simple()
 {
     std::cout << "test" << std::endl;
diff --git a/test/dev/test_api/testextension.h b/test/dev/test_api/testextension.h
index 699d520f..6c88ad25 100644
--- a/test/dev/test_api/testextension.h
+++ b/test/dev/test_api/testextension.h
@@ -20,6 +20,7 @@ class TestExtension : public IExtension
         static CompilerValue *compilePrintField(Compiler *compiler);
         static CompilerValue *compileTestStr(Compiler *compiler);
         static CompilerValue *compileInput(Compiler *compiler);
+        static CompilerValue *compileSubstack(Compiler *compiler);
 };
 
 } // namespace libscratchcpp

From f98e1d1bc8bfc1ec407eae0de2bb82b6075369fc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 14 Dec 2024 18:05:44 +0100
Subject: [PATCH 140/259] Implement control_foreach

---
 src/dev/blocks/controlblocks.cpp        |  13 +++
 src/dev/blocks/controlblocks.h          |   1 +
 test/dev/blocks/control_blocks_test.cpp | 108 ++++++++++++++++++++++++
 test/dev/blocks/util.cpp                |   8 ++
 4 files changed, 130 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 411940e4..6da0e883 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -9,6 +9,7 @@
 #include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/thread.h>
 #include <scratchcpp/istacktimer.h>
+#include <scratchcpp/variable.h>
 
 #include "controlblocks.h"
 
@@ -35,6 +36,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
     engine->addCompileFunction(this, "control_repeat_until", &compileRepeatUntil);
     engine->addCompileFunction(this, "control_while", &compileWhile);
+    engine->addCompileFunction(this, "control_for_each", &compileForEach);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -123,6 +125,17 @@ CompilerValue *ControlBlocks::compileWhile(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileForEach(Compiler *compiler)
+{
+    Variable *var = static_cast<Variable *>(compiler->field("VARIABLE")->valuePtr().get());
+    assert(var);
+    auto substack = compiler->input("SUBSTACK");
+    compiler->moveToRepeatLoop(compiler->addInput("VALUE"), substack ? substack->valueBlock() : nullptr);
+    auto index = compiler->createAdd(compiler->addLoopIndex(), compiler->addConstValue(1));
+    compiler->createVariableWrite(var, index);
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index d2722809..a68490d1 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -25,6 +25,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileWaitUntil(Compiler *compiler);
         static CompilerValue *compileRepeatUntil(Compiler *compiler);
         static CompilerValue *compileWhile(Compiler *compiler);
+        static CompilerValue *compileForEach(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 19215797..bad62dc6 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -4,10 +4,12 @@
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
 #include <scratchcpp/script.h>
 #include <scratchcpp/thread.h>
 #include <scratchcpp/dev/executablecode.h>
 #include <scratchcpp/dev/executioncontext.h>
+#include <scratchcpp/variable.h>
 
 #include <enginemock.h>
 #include <stacktimermock.h>
@@ -646,3 +648,109 @@ TEST_F(ControlBlocksTest, While)
         }
     }
 }
+
+TEST_F(ControlBlocksTest, ForEach)
+{
+    auto target = std::make_shared<Sprite>();
+    auto var1 = std::make_shared<Variable>("", "");
+    auto var2 = std::make_shared<Variable>("", "");
+    target->addVariable(var1);
+    target->addVariable(var2);
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_for_each");
+
+        auto substack = std::make_shared<Block>("", "test_print");
+        auto input = std::make_shared<Input>("STRING", Input::Type::ObscuredShadow);
+        input->primaryValue()->setValuePtr(var1);
+        substack->addInput(input);
+
+        builder.addObscuredInput("SUBSTACK", substack);
+
+        builder.addValueInput("VALUE", 5);
+        builder.addEntityField("VARIABLE", var1);
+
+        builder.addBlock("control_for_each");
+        substack = std::make_shared<Block>("", "test_print_test");
+        builder.addObscuredInput("SUBSTACK", substack);
+        builder.addNullObscuredInput("VALUE");
+        builder.addEntityField("VARIABLE", var2);
+
+        builder.build();
+
+        var1->setValue(10);
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "1\n2\n3\n4\n5\n");
+        ASSERT_EQ(var1->value(), 5);
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+    target->addVariable(var1);
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_for_each");
+
+        auto substack = std::make_shared<Block>("", "test_print");
+        auto input = std::make_shared<Input>("STRING", Input::Type::ObscuredShadow);
+        input->primaryValue()->setValuePtr(var1);
+        substack->addInput(input);
+
+        auto setVar = std::make_shared<Block>("", "test_set_var");
+        substack->setNext(setVar);
+        setVar->setParent(substack);
+        auto field = std::make_shared<Field>("VARIABLE", "");
+        setVar->addField(field);
+        input = std::make_shared<Input>("VALUE", Input::Type::Shadow);
+        input->setPrimaryValue(0);
+        setVar->addInput(input);
+
+        auto printAgain = std::make_shared<Block>("", "test_print");
+        setVar->setNext(printAgain);
+        printAgain->setParent(setVar);
+        input = std::make_shared<Input>("STRING", Input::Type::ObscuredShadow);
+        printAgain->addInput(input);
+
+        builder.addObscuredInput("SUBSTACK", substack);
+
+        builder.addValueInput("VALUE", 3);
+        builder.addEntityField("VARIABLE", var1);
+
+        field->setValuePtr(var1);
+        input->primaryValue()->setValuePtr(var1);
+
+        builder.build();
+
+        var1->setValue(7);
+        testing::internal::CaptureStdout();
+        builder.run();
+        ASSERT_EQ(testing::internal::GetCapturedStdout(), "1\n0\n2\n0\n3\n0\n");
+        ASSERT_EQ(var1->value(), 0);
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+    target->addVariable(var1);
+
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+        builder.addBlock("control_for_each");
+        builder.addValueInput("VALUE", "Infinity");
+        builder.addEntityField("VARIABLE", var1);
+
+        builder.build();
+        m_engine->start();
+
+        for (int i = 0; i < 2; i++) {
+            m_engine->step();
+            ASSERT_TRUE(m_engine->isRunning());
+        }
+
+        ASSERT_GT(var1->value(), 0);
+    }
+}
diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp
index a6890119..2881c3f5 100644
--- a/test/dev/blocks/util.cpp
+++ b/test/dev/blocks/util.cpp
@@ -2,6 +2,8 @@
 #include <scratchcpp/dev/compiler.h>
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/value.h>
+#include <scratchcpp/field.h>
+#include <scratchcpp/variable.h>
 #include <iostream>
 
 #include "util.h"
@@ -30,6 +32,12 @@ void registerBlocks(IEngine *engine, IExtension *extension)
     });
 
     engine->addCompileFunction(extension, "test_condition", [](Compiler *compiler) -> CompilerValue * { return compiler->addFunctionCall("test_condition", Compiler::StaticType::Bool); });
+
+    engine->addCompileFunction(extension, "test_set_var", [](Compiler *compiler) -> CompilerValue * {
+        Variable *var = static_cast<Variable *>(compiler->field("VARIABLE")->valuePtr().get());
+        compiler->createVariableWrite(var, compiler->addInput("VALUE"));
+        return nullptr;
+    });
 }
 
 extern "C" void test_print(const char *str)

From 6482b86223dffa65ddc2c62a56f539c699d88c79 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 16 Dec 2024 07:47:20 +0100
Subject: [PATCH 141/259] Implement control_start_as_clone

---
 src/dev/blocks/controlblocks.cpp        |  7 +++++++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 13 +++++++++++++
 3 files changed, 21 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 6da0e883..e49c2b25 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -37,6 +37,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_repeat_until", &compileRepeatUntil);
     engine->addCompileFunction(this, "control_while", &compileWhile);
     engine->addCompileFunction(this, "control_for_each", &compileForEach);
+    engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -136,6 +137,12 @@ CompilerValue *ControlBlocks::compileForEach(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileStartAsClone(Compiler *compiler)
+{
+    compiler->engine()->addCloneInitScript(compiler->block());
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index a68490d1..4e7e3b16 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -26,6 +26,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileRepeatUntil(Compiler *compiler);
         static CompilerValue *compileWhile(Compiler *compiler);
         static CompilerValue *compileForEach(Compiler *compiler);
+        static CompilerValue *compileStartAsClone(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index bad62dc6..9416b56b 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -754,3 +754,16 @@ TEST_F(ControlBlocksTest, ForEach)
         ASSERT_GT(var1->value(), 0);
     }
 }
+
+TEST_F(ControlBlocksTest, StartAsClone)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("control_start_as_clone");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    EXPECT_CALL(m_engineMock, addCloneInitScript(block));
+    compiler.compile(block);
+}

From 1cd40167c9fde36b975abbc89f8c0eaf7138606d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 16 Dec 2024 15:26:23 +0100
Subject: [PATCH 142/259] Implement control_create_clone_of

---
 src/dev/blocks/controlblocks.cpp        |  52 +++++
 src/dev/blocks/controlblocks.h          |   1 +
 test/dev/blocks/control_blocks_test.cpp | 296 ++++++++++++++++++++++++
 test/dev/blocks/util.cpp                |   2 +
 4 files changed, 351 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index e49c2b25..d9c66837 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -10,6 +10,7 @@
 #include <scratchcpp/thread.h>
 #include <scratchcpp/istacktimer.h>
 #include <scratchcpp/variable.h>
+#include <scratchcpp/sprite.h>
 
 #include "controlblocks.h"
 
@@ -38,6 +39,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_while", &compileWhile);
     engine->addCompileFunction(this, "control_for_each", &compileForEach);
     engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone);
+    engine->addCompileFunction(this, "control_create_clone_of", &compileCreateCloneOf);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -143,6 +145,28 @@ CompilerValue *ControlBlocks::compileStartAsClone(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileCreateCloneOf(Compiler *compiler)
+{
+    Input *input = compiler->input("CLONE_OPTION");
+
+    if (input->pointsToDropdownMenu()) {
+        std::string spriteName = input->selectedMenuItem();
+
+        if (spriteName == "_myself_")
+            compiler->addTargetFunctionCall("control_create_clone_of_myself");
+        else {
+            auto index = compiler->engine()->findTarget(spriteName);
+            CompilerValue *arg = compiler->addConstValue(index);
+            compiler->addFunctionCallWithCtx("control_create_clone_by_index", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { arg });
+        }
+    } else {
+        CompilerValue *arg = compiler->addInput("CLONE_OPTION");
+        compiler->addFunctionCallWithCtx("control_create_clone", Compiler::StaticType::Void, { Compiler::StaticType::String }, { arg });
+    }
+
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
@@ -164,3 +188,31 @@ extern "C" bool control_stack_timer_elapsed(ExecutionContext *ctx)
 {
     return ctx->stackTimer()->elapsed();
 }
+
+extern "C" void control_create_clone_of_myself(Target *target)
+{
+    if (!target->isStage())
+        static_cast<Sprite *>(target)->clone();
+}
+
+extern "C" void control_create_clone_by_index(ExecutionContext *ctx, double index)
+{
+    Target *target = ctx->engine()->targetAt(index);
+
+    if (!target->isStage())
+        static_cast<Sprite *>(target)->clone();
+}
+
+extern "C" void control_create_clone(ExecutionContext *ctx, const char *spriteName)
+{
+    if (strcmp(spriteName, "_myself_") == 0)
+        control_create_clone_of_myself(ctx->thread()->target());
+    else {
+        IEngine *engine = ctx->engine();
+        auto index = engine->findTarget(spriteName);
+        Target *target = engine->targetAt(index);
+
+        if (!target->isStage())
+            static_cast<Sprite *>(target)->clone();
+    }
+}
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 4e7e3b16..51012ad0 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -27,6 +27,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileWhile(Compiler *compiler);
         static CompilerValue *compileForEach(Compiler *compiler);
         static CompilerValue *compileStartAsClone(Compiler *compiler);
+        static CompilerValue *compileCreateCloneOf(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 9416b56b..09091052 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -2,6 +2,7 @@
 #include <scratchcpp/dev/compiler.h>
 #include <scratchcpp/dev/test/scriptbuilder.h>
 #include <scratchcpp/sprite.h>
+#include <scratchcpp/stage.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/input.h>
 #include <scratchcpp/field.h>
@@ -22,6 +23,8 @@ using namespace libscratchcpp;
 using namespace libscratchcpp::test;
 
 using ::testing::Return;
+using ::testing::SaveArg;
+using ::testing::_;
 
 class ControlBlocksTest : public testing::Test
 {
@@ -767,3 +770,296 @@ TEST_F(ControlBlocksTest, StartAsClone)
     EXPECT_CALL(m_engineMock, addCloneInitScript(block));
     compiler.compile(block);
 }
+
+TEST_F(ControlBlocksTest, CreateCloneOfSprite)
+{
+    EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1));
+    EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return());
+    auto target = std::make_shared<Sprite>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of [Sprite1]
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        builder.addDropdownInput("CLONE_OPTION", "Sprite1");
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget("Sprite1")).WillOnce(Return(4));
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        Sprite sprite;
+        sprite.setEngine(&m_engineMock);
+        std::shared_ptr<Sprite> clone;
+        EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
+        EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+        EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
+        thread.run();
+        ASSERT_TRUE(clone);
+        ASSERT_EQ(clone->cloneSprite(), &sprite);
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of [myself]
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        builder.addDropdownInput("CLONE_OPTION", "_myself_");
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        std::shared_ptr<Sprite> clone;
+        EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+        EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, target.get()));
+        thread.run();
+        ASSERT_TRUE(clone);
+        ASSERT_EQ(clone->cloneSprite(), target.get());
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of ["_mYself_"]
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        builder.addDropdownInput("CLONE_OPTION", "_mYself_");
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget("_mYself_")).WillOnce(Return(4));
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        Sprite sprite;
+        sprite.setEngine(&m_engineMock);
+        std::shared_ptr<Sprite> clone;
+        EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
+        EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+        EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
+        thread.run();
+        ASSERT_TRUE(clone);
+        ASSERT_EQ(clone->cloneSprite(), &sprite);
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of (null block)
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        builder.addNullObscuredInput("CLONE_OPTION");
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        Sprite sprite;
+        sprite.setEngine(&m_engineMock);
+        std::shared_ptr<Sprite> clone;
+        EXPECT_CALL(m_engineMock, findTarget("0")).WillOnce(Return(2));
+        EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&sprite));
+        EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+        EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
+        thread.run();
+        ASSERT_TRUE(clone);
+        ASSERT_EQ(clone->cloneSprite(), &sprite);
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Sprite>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of ("_myself_")
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        auto valueBlock = std::make_shared<Block>("", "test_input");
+        auto input = std::make_shared<Input>("INPUT", Input::Type::Shadow);
+        input->setPrimaryValue("_myself_");
+        valueBlock->addInput(input);
+        builder.addObscuredInput("CLONE_OPTION", valueBlock);
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        std::shared_ptr<Sprite> clone;
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+        EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, target.get()));
+        thread.run();
+        ASSERT_TRUE(clone);
+        ASSERT_EQ(clone->cloneSprite(), target.get());
+    }
+
+    // create clone of ("_mYself_")
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        auto valueBlock = std::make_shared<Block>("", "test_input");
+        auto input = std::make_shared<Input>("INPUT", Input::Type::Shadow);
+        input->setPrimaryValue("_mYself_");
+        valueBlock->addInput(input);
+        builder.addObscuredInput("CLONE_OPTION", valueBlock);
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        Sprite sprite;
+        sprite.setEngine(&m_engineMock);
+        std::shared_ptr<Sprite> clone;
+        EXPECT_CALL(m_engineMock, findTarget("_mYself_")).WillOnce(Return(2));
+        EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&sprite));
+        EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+        EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
+        thread.run();
+        ASSERT_TRUE(clone);
+        ASSERT_EQ(clone->cloneSprite(), &sprite);
+    }
+}
+
+TEST_F(ControlBlocksTest, CreateCloneOfStage)
+{
+    EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1));
+    EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return());
+    auto target = std::make_shared<Stage>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of [Stage]
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        builder.addDropdownInput("CLONE_OPTION", "_stage_");
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(8));
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        Stage stage;
+        stage.setEngine(&m_engineMock);
+        EXPECT_CALL(m_engineMock, targetAt(8)).WillOnce(Return(&stage));
+        EXPECT_CALL(m_engineMock, initClone).Times(0);
+        thread.run();
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Stage>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of [myself]
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        builder.addDropdownInput("CLONE_OPTION", "_myself_");
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, initClone).Times(0);
+        thread.run();
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Stage>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of (null block)
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        builder.addNullObscuredInput("CLONE_OPTION");
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        Stage stage;
+        stage.setEngine(&m_engineMock);
+        EXPECT_CALL(m_engineMock, findTarget("0")).WillOnce(Return(2));
+        EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&stage));
+        EXPECT_CALL(m_engineMock, initClone).Times(0);
+        thread.run();
+    }
+
+    m_engine->clear();
+    target = std::make_shared<Stage>();
+    target->setEngine(&m_engineMock);
+
+    // create clone of ("_myself_")
+    {
+        ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+        builder.addBlock("control_create_clone_of");
+        auto valueBlock = std::make_shared<Block>("", "test_input");
+        auto input = std::make_shared<Input>("INPUT", Input::Type::Shadow);
+        input->setPrimaryValue("_myself_");
+        valueBlock->addInput(input);
+        builder.addObscuredInput("CLONE_OPTION", valueBlock);
+        auto block = builder.currentBlock();
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        Compiler compiler(&m_engineMock, target.get());
+        auto code = compiler.compile(block);
+        Script script(target.get(), block, &m_engineMock);
+        script.setCode(code);
+        Thread thread(target.get(), &m_engineMock, &script);
+
+        EXPECT_CALL(m_engineMock, findTarget).Times(0);
+        EXPECT_CALL(m_engineMock, initClone).Times(0);
+        thread.run();
+    }
+}
diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp
index 2881c3f5..78345800 100644
--- a/test/dev/blocks/util.cpp
+++ b/test/dev/blocks/util.cpp
@@ -33,6 +33,8 @@ void registerBlocks(IEngine *engine, IExtension *extension)
 
     engine->addCompileFunction(extension, "test_condition", [](Compiler *compiler) -> CompilerValue * { return compiler->addFunctionCall("test_condition", Compiler::StaticType::Bool); });
 
+    engine->addCompileFunction(extension, "test_input", [](Compiler *compiler) -> CompilerValue * { return compiler->addInput("INPUT"); });
+
     engine->addCompileFunction(extension, "test_set_var", [](Compiler *compiler) -> CompilerValue * {
         Variable *var = static_cast<Variable *>(compiler->field("VARIABLE")->valuePtr().get());
         compiler->createVariableWrite(var, compiler->addInput("VALUE"));

From 2388d9f0935357ad54256799313d66767435357f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 16 Dec 2024 21:39:48 +0100
Subject: [PATCH 143/259] Implement control_delete_this_clone

---
 src/dev/blocks/controlblocks.cpp        | 15 ++++++++
 src/dev/blocks/controlblocks.h          |  1 +
 test/dev/blocks/control_blocks_test.cpp | 50 +++++++++++++++++++++++++
 3 files changed, 66 insertions(+)

diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index d9c66837..9567f69f 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -40,6 +40,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_for_each", &compileForEach);
     engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone);
     engine->addCompileFunction(this, "control_create_clone_of", &compileCreateCloneOf);
+    engine->addCompileFunction(this, "control_delete_this_clone", &compileDeleteThisClone);
 }
 
 CompilerValue *ControlBlocks::compileForever(Compiler *compiler)
@@ -167,6 +168,12 @@ CompilerValue *ControlBlocks::compileCreateCloneOf(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *ControlBlocks::compileDeleteThisClone(Compiler *compiler)
+{
+    compiler->addTargetFunctionCall("control_delete_this_clone");
+    return nullptr;
+}
+
 extern "C" void control_stop_all(ExecutionContext *ctx)
 {
     ctx->engine()->stop();
@@ -216,3 +223,11 @@ extern "C" void control_create_clone(ExecutionContext *ctx, const char *spriteNa
             static_cast<Sprite *>(target)->clone();
     }
 }
+
+extern "C" void control_delete_this_clone(Target *target)
+{
+    if (!target->isStage()) {
+        target->engine()->stopTarget(target, nullptr);
+        static_cast<Sprite *>(target)->deleteClone();
+    }
+}
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 51012ad0..17850824 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -28,6 +28,7 @@ class ControlBlocks : public IExtension
         static CompilerValue *compileForEach(Compiler *compiler);
         static CompilerValue *compileStartAsClone(Compiler *compiler);
         static CompilerValue *compileCreateCloneOf(Compiler *compiler);
+        static CompilerValue *compileDeleteThisClone(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp
index 09091052..9f054691 100644
--- a/test/dev/blocks/control_blocks_test.cpp
+++ b/test/dev/blocks/control_blocks_test.cpp
@@ -1063,3 +1063,53 @@ TEST_F(ControlBlocksTest, CreateCloneOfStage)
         thread.run();
     }
 }
+
+TEST_F(ControlBlocksTest, DeleteThisClone)
+{
+    Sprite sprite;
+    sprite.setEngine(&m_engineMock);
+
+    std::shared_ptr<Sprite> clone;
+    EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1));
+    EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+    EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite));
+    EXPECT_CALL(m_engineMock, requestRedraw());
+    sprite.clone();
+    ASSERT_TRUE(clone);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, clone);
+
+    builder.addBlock("control_delete_this_clone");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, clone.get());
+    auto code = compiler.compile(block);
+    Script script(clone.get(), block, &m_engineMock);
+    script.setCode(code);
+    Thread thread(clone.get(), &m_engineMock, &script);
+
+    EXPECT_CALL(m_engineMock, stopTarget(clone.get(), nullptr));
+    EXPECT_CALL(m_engineMock, deinitClone(clone));
+    thread.run();
+}
+
+TEST_F(ControlBlocksTest, DeleteThisCloneStage)
+{
+    auto target = std::make_shared<Stage>();
+    target->setEngine(&m_engineMock);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("control_delete_this_clone");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    auto code = compiler.compile(block);
+    Script script(target.get(), block, &m_engineMock);
+    script.setCode(code);
+    Thread thread(target.get(), &m_engineMock, &script);
+
+    EXPECT_CALL(m_engineMock, stopTarget).Times(0);
+    EXPECT_CALL(m_engineMock, deinitClone).Times(0);
+    thread.run();
+}

From 60cc93fb2345345fca5f32b51991530a72a200b7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 22 Dec 2024 16:50:32 +0100
Subject: [PATCH 144/259] Implement operator_add

---
 src/dev/blocks/operatorblocks.cpp        |  9 +++++++
 src/dev/blocks/operatorblocks.h          |  3 +++
 test/dev/blocks/operator_blocks_test.cpp | 34 +++++++++++++++++++++++-
 3 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 8d4cbb92..6ee18e57 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -1,5 +1,8 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+
 #include "operatorblocks.h"
 
 using namespace libscratchcpp;
@@ -16,4 +19,10 @@ std::string OperatorBlocks::description() const
 
 void OperatorBlocks::registerBlocks(IEngine *engine)
 {
+    engine->addCompileFunction(this, "operator_add", &compileAdd);
+}
+
+CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
+{
+    return compiler->createAdd(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
 }
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index af5bd7b5..8c4c924f 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -14,6 +14,9 @@ class OperatorBlocks : public IExtension
         std::string description() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+    private:
+        static CompilerValue *compileAdd(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index f29aacec..e2fbb912 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -1,15 +1,47 @@
+#include <scratchcpp/project.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/list.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/test/scriptbuilder.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "dev/blocks/operatorblocks.h"
 
 using namespace libscratchcpp;
+using namespace libscratchcpp::test;
 
 class OperatorBlocksTest : public testing::Test
 {
     public:
-        void SetUp() override { m_extension = std::make_unique<OperatorBlocks>(); }
+        void SetUp() override
+        {
+            m_extension = std::make_unique<OperatorBlocks>();
+            m_engine = m_project.engine().get();
+            m_extension->registerBlocks(m_engine);
+        }
 
         std::unique_ptr<IExtension> m_extension;
+        Project m_project;
+        IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
 };
+
+TEST_F(OperatorBlocksTest, Add)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_add");
+    builder.addValueInput("NUM1", 5.7);
+    builder.addValueInput("NUM2", 2.5);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 1);
+    ASSERT_EQ(Value(values[0]), 8.2);
+}

From 8ee476830f0b0a9f73ba36929779260bc4cae382 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 22 Dec 2024 16:56:31 +0100
Subject: [PATCH 145/259] Implement operator_subtract

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 19 +++++++++++++++++++
 3 files changed, 26 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 6ee18e57..6be5c594 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -20,9 +20,15 @@ std::string OperatorBlocks::description() const
 void OperatorBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "operator_add", &compileAdd);
+    engine->addCompileFunction(this, "operator_subtract", &compileSubtract);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
 {
     return compiler->createAdd(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
 }
+
+CompilerValue *OperatorBlocks::compileSubtract(Compiler *compiler)
+{
+    return compiler->createSub(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 8c4c924f..30c7a426 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -17,6 +17,7 @@ class OperatorBlocks : public IExtension
 
     private:
         static CompilerValue *compileAdd(Compiler *compiler);
+        static CompilerValue *compileSubtract(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index e2fbb912..21e2a0e6 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -45,3 +45,22 @@ TEST_F(OperatorBlocksTest, Add)
     ASSERT_EQ(valueList->size(), 1);
     ASSERT_EQ(Value(values[0]), 8.2);
 }
+
+TEST_F(OperatorBlocksTest, Subtract)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_subtract");
+    builder.addValueInput("NUM1", 5.7);
+    builder.addValueInput("NUM2", 2.5);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 1);
+    ASSERT_EQ(Value(values[0]), 3.2);
+}

From 19b35d1893054e6fc8071f726940b9d7c9479356 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 22 Dec 2024 17:03:10 +0100
Subject: [PATCH 146/259] Implement operator_multiply

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 19 +++++++++++++++++++
 3 files changed, 26 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 6be5c594..69e8f17f 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -21,6 +21,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "operator_add", &compileAdd);
     engine->addCompileFunction(this, "operator_subtract", &compileSubtract);
+    engine->addCompileFunction(this, "operator_multiply", &compileMultiply);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -32,3 +33,8 @@ CompilerValue *OperatorBlocks::compileSubtract(Compiler *compiler)
 {
     return compiler->createSub(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
 }
+
+CompilerValue *OperatorBlocks::compileMultiply(Compiler *compiler)
+{
+    return compiler->createMul(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 30c7a426..e155c726 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -18,6 +18,7 @@ class OperatorBlocks : public IExtension
     private:
         static CompilerValue *compileAdd(Compiler *compiler);
         static CompilerValue *compileSubtract(Compiler *compiler);
+        static CompilerValue *compileMultiply(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 21e2a0e6..6c28a02c 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -64,3 +64,22 @@ TEST_F(OperatorBlocksTest, Subtract)
     ASSERT_EQ(valueList->size(), 1);
     ASSERT_EQ(Value(values[0]), 3.2);
 }
+
+TEST_F(OperatorBlocksTest, Multiply)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_multiply");
+    builder.addValueInput("NUM1", 5.7);
+    builder.addValueInput("NUM2", 2.5);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 1);
+    ASSERT_EQ(Value(values[0]), 14.25);
+}

From 7b9df371a4763fb1e3d7deaf1746b105ef996313 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 22 Dec 2024 17:06:07 +0100
Subject: [PATCH 147/259] Implement operator_divide

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 19 +++++++++++++++++++
 3 files changed, 26 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 69e8f17f..f98a924b 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -22,6 +22,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_add", &compileAdd);
     engine->addCompileFunction(this, "operator_subtract", &compileSubtract);
     engine->addCompileFunction(this, "operator_multiply", &compileMultiply);
+    engine->addCompileFunction(this, "operator_divide", &compileDivide);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -38,3 +39,8 @@ CompilerValue *OperatorBlocks::compileMultiply(Compiler *compiler)
 {
     return compiler->createMul(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
 }
+
+CompilerValue *OperatorBlocks::compileDivide(Compiler *compiler)
+{
+    return compiler->createDiv(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index e155c726..89b7a1d6 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -19,6 +19,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileAdd(Compiler *compiler);
         static CompilerValue *compileSubtract(Compiler *compiler);
         static CompilerValue *compileMultiply(Compiler *compiler);
+        static CompilerValue *compileDivide(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 6c28a02c..13282adb 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -83,3 +83,22 @@ TEST_F(OperatorBlocksTest, Multiply)
     ASSERT_EQ(valueList->size(), 1);
     ASSERT_EQ(Value(values[0]), 14.25);
 }
+
+TEST_F(OperatorBlocksTest, Divide)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_divide");
+    builder.addValueInput("NUM1", 5.7);
+    builder.addValueInput("NUM2", 2.5);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 1);
+    ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 2.28);
+}

From b611cb1290a1090c60140848f6618c37bc55ba29 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 13:33:06 +0100
Subject: [PATCH 148/259] LLVMCodeBuilder: Disable no infinity fast math flag

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 069f7bd9..959871ee 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -53,6 +53,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     // Set fast math flags
     llvm::FastMathFlags fmf;
     fmf.setFast(true);
+    fmf.setNoInfs(false);
     fmf.setNoNaNs(false);
     fmf.setNoSignedZeros(false);
     m_builder.setFastMathFlags(fmf);

From 04793097715fea412170509e0f89cf76c334fb64 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 14:11:55 +0100
Subject: [PATCH 149/259] Add value_doubleIsInt() function

---
 include/scratchcpp/value_functions.h |  2 ++
 src/scratch/value_functions.cpp      | 20 +++++++++++++-------
 test/scratch_classes/value_test.cpp  | 10 ++++++++++
 3 files changed, 25 insertions(+), 7 deletions(-)

diff --git a/include/scratchcpp/value_functions.h b/include/scratchcpp/value_functions.h
index d4409a0b..1542d2b9 100644
--- a/include/scratchcpp/value_functions.h
+++ b/include/scratchcpp/value_functions.h
@@ -34,6 +34,8 @@ extern "C"
     LIBSCRATCHCPP_EXPORT char *value_toCString(const ValueData *v);
     LIBSCRATCHCPP_EXPORT void value_toUtf16(const ValueData *v, std::u16string *dst);
 
+    LIBSCRATCHCPP_EXPORT bool value_doubleIsInt(double v);
+
     LIBSCRATCHCPP_EXPORT char *value_doubleToCString(double v);
     LIBSCRATCHCPP_EXPORT const char *value_boolToCString(bool v);
     LIBSCRATCHCPP_EXPORT double value_stringToDouble(const char *s);
diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp
index 710facd5..0da37ff3 100644
--- a/src/scratch/value_functions.cpp
+++ b/src/scratch/value_functions.cpp
@@ -164,14 +164,9 @@ extern "C"
             case ValueType::Bool:
                 return true;
 
-            case ValueType::Number: {
-                if (std::isinf(v->numberValue) || std::isnan(v->numberValue))
-                    return true;
+            case ValueType::Number:
+                return value_doubleIsInt(v->numberValue);
 
-                double intpart;
-                std::modf(v->numberValue, &intpart);
-                return v->numberValue == intpart;
-            }
             case ValueType::String:
                 return value_checkString(v->stringValue) == 1;
         }
@@ -280,6 +275,17 @@ extern "C"
         dst->assign(utf8::utf8to16(s));
     }
 
+    /*! Returns true if the given number represents a round integer. */
+    bool value_doubleIsInt(double v)
+    {
+        if (std::isinf(v) || std::isnan(v))
+            return true;
+
+        double intpart;
+        std::modf(v, &intpart);
+        return v == intpart;
+    }
+
     /*!
      * Converts the given number to string.
      * \note It is the caller's responsibility to free allocated memory.
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 04cce470..f59f3078 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -2795,6 +2795,16 @@ TEST(ValueTest, ComparisonOperators)
     }
 }
 
+TEST(ValueTest, DoubleIsInt)
+{
+    ASSERT_TRUE(value_doubleIsInt(0.0));
+    ASSERT_TRUE(value_doubleIsInt(15.0));
+    ASSERT_TRUE(value_doubleIsInt(-468.0));
+    ASSERT_FALSE(value_doubleIsInt(0.1));
+    ASSERT_FALSE(value_doubleIsInt(1.2));
+    ASSERT_FALSE(value_doubleIsInt(-12.5852));
+}
+
 TEST(ValueTest, DoubleToCString)
 {
     char *ret;

From 1a68af02353587bd3480cc334c02cb87aa844acb Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 14:13:49 +0100
Subject: [PATCH 150/259] LLVMCodeBuilder: Add createRandom() method

---
 src/dev/engine/internal/icodebuilder.h        |   2 +
 src/dev/engine/internal/llvm/CMakeLists.txt   |   2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  53 ++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |   5 +
 .../engine/internal/llvm/llvmfunctions.cpp    |  38 ++++++
 src/dev/engine/internal/llvm/llvmfunctions.h  |  11 ++
 .../engine/internal/llvm/llvminstruction.h    |   1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 125 +++++++++++++++++-
 test/mocks/codebuildermock.h                  |   2 +
 9 files changed, 234 insertions(+), 5 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmfunctions.cpp
 create mode 100644 src/dev/engine/internal/llvm/llvmfunctions.h

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index c92c3222..d65a24f0 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -36,6 +36,8 @@ class ICodeBuilder
         virtual CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) = 0;
 
+        virtual CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) = 0;
+
         virtual CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) = 0;
diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index 4ef97a05..7bd34c4d 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -15,6 +15,8 @@ target_sources(scratchcpp
     llvmprocedure.h
     llvmtypes.cpp
     llvmtypes.h
+    llvmfunctions.cpp
+    llvmfunctions.h
     llvmexecutablecode.cpp
     llvmexecutablecode.h
     llvmexecutioncontext.cpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 959871ee..c348b387 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -191,6 +191,38 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::Random: {
+                assert(step.args.size() == 2);
+                const auto &arg1 = step.args[0];
+                const auto &arg2 = step.args[1];
+                LLVMRegister *reg1 = arg1.second;
+                LLVMRegister *reg2 = arg2.second;
+
+                if (reg1->type() == Compiler::StaticType::Bool && reg2->type() == Compiler::StaticType::Bool) {
+                    llvm::Value *bool1 = castValue(arg1.second, Compiler::StaticType::Bool);
+                    llvm::Value *bool2 = castValue(arg2.second, Compiler::StaticType::Bool);
+                    step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_bool(), { bool1, bool2 });
+                } else {
+                    llvm::Constant *inf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false);
+                    llvm::Value *num1 = removeNaN(castValue(arg1.second, Compiler::StaticType::Number));
+                    llvm::Value *num2 = removeNaN(castValue(arg2.second, Compiler::StaticType::Number));
+                    llvm::Value *sum = m_builder.CreateFAdd(num1, num2);
+                    llvm::Value *sumDiv = m_builder.CreateFDiv(sum, inf);
+                    llvm::Value *isInfOrNaN = isNaN(sumDiv);
+
+                    // NOTE: The random function will be called even in edge cases where it isn't needed, but they're rare, so it shouldn't be an issue
+                    if (reg1->type() == Compiler::StaticType::Number && reg2->type() == Compiler::StaticType::Number)
+                        step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random_double(), { num1, num2 }));
+                    else {
+                        llvm::Value *value1 = createValue(reg1);
+                        llvm::Value *value2 = createValue(reg2);
+                        step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random(), { value1, value2 }));
+                    }
+                }
+
+                break;
+            }
+
             case LLVMInstruction::Type::CmpEQ: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0].second;
@@ -1089,6 +1121,11 @@ CompilerValue *LLVMCodeBuilder::createDiv(CompilerValue *operand1, CompilerValue
     return createOp(LLVMInstruction::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
+CompilerValue *LLVMCodeBuilder::createRandom(CompilerValue *from, CompilerValue *to)
+{
+    return createOp(LLVMInstruction::Type::Random, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { from, to });
+}
+
 CompilerValue *LLVMCodeBuilder::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2)
 {
     return createOp(LLVMInstruction::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 });
@@ -2334,6 +2371,22 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string()
     return resolveFunction("list_to_string", llvm::FunctionType::get(pointerType, { pointerType }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random()
+{
+    llvm::Type *valuePtr = m_valueDataType->getPointerTo();
+    return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { valuePtr, valuePtr }, false));
+}
+
+llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double()
+{
+    return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false));
+}
+
+llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool()
+{
+    return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index afdf12de..62e4aadf 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -43,6 +43,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) override;
 
+        CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) override;
+
         CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) override;
@@ -166,6 +168,9 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_size_ptr();
         llvm::FunctionCallee resolve_list_alloc_size_ptr();
         llvm::FunctionCallee resolve_list_to_string();
+        llvm::FunctionCallee resolve_llvm_random();
+        llvm::FunctionCallee resolve_llvm_random_double();
+        llvm::FunctionCallee resolve_llvm_random_bool();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvmfunctions.cpp b/src/dev/engine/internal/llvm/llvmfunctions.cpp
new file mode 100644
index 00000000..8664ceb2
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmfunctions.cpp
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/value_functions.h>
+
+#include "llvmfunctions.h"
+#include "../../../../engine/internal/randomgenerator.h"
+
+namespace libscratchcpp
+{
+
+extern "C"
+{
+    double llvm_random(ValueData *from, ValueData *to)
+    {
+        if (!llvm_rng)
+            llvm_rng = RandomGenerator::instance().get();
+
+        return value_isInt(from) && value_isInt(to) ? llvm_rng->randint(value_toLong(from), value_toLong(to)) : llvm_rng->randintDouble(value_toDouble(from), value_toDouble(to));
+    }
+
+    double llvm_random_double(double from, double to)
+    {
+        if (!llvm_rng)
+            llvm_rng = RandomGenerator::instance().get();
+
+        return value_doubleIsInt(from) && value_doubleIsInt(to) ? llvm_rng->randint(from, to) : llvm_rng->randintDouble(from, to);
+    }
+
+    double llvm_random_bool(bool from, bool to)
+    {
+        if (!llvm_rng)
+            llvm_rng = RandomGenerator::instance().get();
+
+        return llvm_rng->randint(from, to);
+    }
+}
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmfunctions.h b/src/dev/engine/internal/llvm/llvmfunctions.h
new file mode 100644
index 00000000..cf078872
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmfunctions.h
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+#pragma once
+
+namespace libscratchcpp
+{
+
+class IRandomGenerator;
+
+IRandomGenerator *llvm_rng = nullptr;
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 9b81b563..f3d2fa7b 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -18,6 +18,7 @@ struct LLVMInstruction
             Sub,
             Mul,
             Div,
+            Random,
             CmpEQ,
             CmpGT,
             CmpLT,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 6fd62ac1..a67dd965 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -7,9 +7,11 @@
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
 #include <dev/engine/internal/llvm/llvmcodebuilder.h>
+#include <dev/engine/internal/llvm/llvmfunctions.h>
 #include <gmock/gmock.h>
 #include <targetmock.h>
 #include <enginemock.h>
+#include <randomgeneratormock.h>
 
 #include "testfunctions.h"
 
@@ -27,6 +29,7 @@ class LLVMCodeBuilderTest : public testing::Test
             Sub,
             Mul,
             Div,
+            Random,
             CmpEQ,
             CmpGT,
             CmpLT,
@@ -49,7 +52,6 @@ class LLVMCodeBuilderTest : public testing::Test
             Log10,
             Exp,
             Exp10
-
         };
 
         void SetUp() override
@@ -93,6 +95,9 @@ class LLVMCodeBuilderTest : public testing::Test
                 case OpType::Div:
                     return m_builder->createDiv(arg1, arg2);
 
+                case OpType::Random:
+                    return m_builder->createRandom(arg1, arg2);
+
                 case OpType::CmpEQ:
                     return m_builder->createCmpEQ(arg1, arg2);
 
@@ -189,6 +194,15 @@ class LLVMCodeBuilderTest : public testing::Test
                 case OpType::Div:
                     return v1 / v2;
 
+                case OpType::Random: {
+                    const double sum = v1.toDouble() + v2.toDouble();
+
+                    if (std::isnan(sum) || std::isinf(sum))
+                        return sum;
+
+                    return v1.isInt() && v2.isInt() ? m_rng.randint(v1.toLong(), v2.toLong()) : m_rng.randintDouble(v1.toDouble(), v2.toDouble());
+                }
+
                 case OpType::CmpEQ:
                     return v1 == v2;
 
@@ -225,7 +239,7 @@ class LLVMCodeBuilderTest : public testing::Test
             }
         }
 
-        void runOpTest(OpType type, const Value &v1, const Value &v2)
+        void runOpTestCommon(OpType type, const Value &v1, const Value &v2)
         {
             createBuilder(true);
 
@@ -241,9 +255,6 @@ class LLVMCodeBuilderTest : public testing::Test
             ret = addOp(type, arg1, arg2);
             m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret });
 
-            std::string str = doOp(type, v1, v2).toString() + '\n';
-            std::string expected = str + str;
-
             auto code = m_builder->finalize();
             Script script(&m_target, nullptr, nullptr);
             script.setCode(code);
@@ -252,9 +263,28 @@ class LLVMCodeBuilderTest : public testing::Test
 
             testing::internal::CaptureStdout();
             code->run(ctx.get());
+        }
+
+        void checkOpTest(const Value &v1, const Value &v2, const std::string &expected)
+        {
             const std::string quotes1 = v1.isString() ? "\"" : "";
             const std::string quotes2 = v2.isString() ? "\"" : "";
             ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2;
+        }
+
+        void runOpTest(OpType type, const Value &v1, const Value &v2, const Value &expected)
+        {
+            std::string str = expected.toString();
+            runOpTestCommon(type, v1, v2);
+            checkOpTest(v1, v2, str + '\n' + str + '\n');
+        };
+
+        void runOpTest(OpType type, const Value &v1, const Value &v2)
+        {
+            runOpTestCommon(type, v1, v2);
+            std::string str = doOp(type, v1, v2).toString() + '\n';
+            std::string expected = str + str;
+            checkOpTest(v1, v2, expected);
         };
 
         void runOpTest(OpType type, const Value &v)
@@ -317,6 +347,7 @@ class LLVMCodeBuilderTest : public testing::Test
 
         std::unique_ptr<LLVMCodeBuilder> m_builder;
         TargetMock m_target; // NOTE: isStage() is used for call expectations
+        RandomGeneratorMock m_rng;
 };
 
 TEST_F(LLVMCodeBuilderTest, FunctionCalls)
@@ -605,6 +636,90 @@ TEST_F(LLVMCodeBuilderTest, Divide)
     runOpTest(OpType::Div, 0, 0);
 }
 
+TEST_F(LLVMCodeBuilderTest, Random)
+{
+    llvm_rng = &m_rng;
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18));
+    runOpTest(OpType::Random, -45, 12);
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5));
+    runOpTest(OpType::Random, -45.0, 12.0);
+
+    EXPECT_CALL(m_rng, randintDouble(12, 6.05)).Times(3).WillRepeatedly(Return(3.486789));
+    runOpTest(OpType::Random, 12, 6.05);
+
+    EXPECT_CALL(m_rng, randintDouble(-78.686, -45)).Times(3).WillRepeatedly(Return(-59.468873));
+    runOpTest(OpType::Random, -78.686, -45);
+
+    EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(-28.648764));
+    runOpTest(OpType::Random, 6.05, -78.686);
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0));
+    runOpTest(OpType::Random, "-45", "12");
+
+    EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(5.2));
+    runOpTest(OpType::Random, "-45.0", "12");
+
+    EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(-15.5787));
+    runOpTest(OpType::Random, "-45", "12.0");
+
+    EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(2.587964));
+    runOpTest(OpType::Random, "-45.0", "12.0");
+
+    EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764));
+    runOpTest(OpType::Random, "6.05", "-78.686");
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0));
+    runOpTest(OpType::Random, "-45", 12);
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0));
+    runOpTest(OpType::Random, -45, "12");
+
+    EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(5.2));
+    runOpTest(OpType::Random, "-45.0", 12);
+
+    EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(-15.5787));
+    runOpTest(OpType::Random, -45, "12.0");
+
+    EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764));
+    runOpTest(OpType::Random, 6.05, "-78.686");
+
+    EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764));
+    runOpTest(OpType::Random, "6.05", -78.686);
+
+    EXPECT_CALL(m_rng, randint(0, 1)).Times(3).WillRepeatedly(Return(1));
+    runOpTest(OpType::Random, false, true);
+
+    EXPECT_CALL(m_rng, randint(1, 5)).Times(3).WillRepeatedly(Return(1));
+    runOpTest(OpType::Random, true, 5);
+
+    EXPECT_CALL(m_rng, randint(8, 0)).Times(3).WillRepeatedly(Return(1));
+    runOpTest(OpType::Random, 8, false);
+
+    const double inf = std::numeric_limits<double>::infinity();
+    const double nan = std::numeric_limits<double>::quiet_NaN();
+    EXPECT_CALL(m_rng, randint).WillRepeatedly(Return(0));
+    EXPECT_CALL(m_rng, randintDouble).WillRepeatedly(Return(0));
+
+    runOpTest(OpType::Random, inf, 2, inf);
+    runOpTest(OpType::Random, -8, inf, inf);
+    runOpTest(OpType::Random, -inf, -2, -inf);
+    runOpTest(OpType::Random, 8, -inf, -inf);
+
+    runOpTest(OpType::Random, inf, 2.5, inf);
+    runOpTest(OpType::Random, -8.09, inf, inf);
+    runOpTest(OpType::Random, -inf, -2.5, -inf);
+    runOpTest(OpType::Random, 8.09, -inf, -inf);
+
+    runOpTest(OpType::Random, inf, inf, inf);
+    runOpTest(OpType::Random, -inf, -inf, -inf);
+    runOpTest(OpType::Random, inf, -inf, nan);
+    runOpTest(OpType::Random, -inf, inf, nan);
+
+    llvm_rng = nullptr;
+}
+
 TEST_F(LLVMCodeBuilderTest, EqualComparison)
 {
     runOpTest(OpType::CmpEQ, 10, 10);
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 478252d6..bd381144 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -26,6 +26,8 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(CompilerValue *, createMul, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createDiv, (CompilerValue *, CompilerValue *), (override));
 
+        MOCK_METHOD(CompilerValue *, createRandom, (CompilerValue *, CompilerValue *), (override));
+
         MOCK_METHOD(CompilerValue *, createCmpEQ, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createCmpGT, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createCmpLT, (CompilerValue *, CompilerValue *), (override));

From 5736a0fe2f7094d3d4566939031eec9cc1da66b6 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 14:55:49 +0100
Subject: [PATCH 151/259] Move IRandomGenerator to public API

---
 CMakeLists.txt                         |  1 +
 include/scratchcpp/irandomgenerator.h  | 26 ++++++++++++++++++++++++++
 src/engine/CMakeLists.txt              |  1 -
 src/engine/internal/irandomgenerator.h | 18 ------------------
 src/engine/internal/randomgenerator.h  |  3 +--
 test/mocks/randomgeneratormock.h       |  2 +-
 6 files changed, 29 insertions(+), 22 deletions(-)
 create mode 100644 include/scratchcpp/irandomgenerator.h
 delete mode 100644 src/engine/internal/irandomgenerator.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index c08d26ae..1aaebf77 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -60,6 +60,7 @@ target_sources(scratchcpp
     include/scratchcpp/textbubble.h
     include/scratchcpp/itimer.h
     include/scratchcpp/istacktimer.h
+    include/scratchcpp/irandomgenerator.h
     include/scratchcpp/keyevent.h
     include/scratchcpp/rect.h
     include/scratchcpp/igraphicseffect.h
diff --git a/include/scratchcpp/irandomgenerator.h b/include/scratchcpp/irandomgenerator.h
new file mode 100644
index 00000000..1fa1d79c
--- /dev/null
+++ b/include/scratchcpp/irandomgenerator.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "global.h"
+
+namespace libscratchcpp
+{
+
+/*! \brief The IRandomGenerator interface represents a random number generator that can be received e. g. from an ExecutionContext. */
+class LIBSCRATCHCPP_EXPORT IRandomGenerator
+{
+    public:
+        virtual ~IRandomGenerator() { }
+
+        /*! Returns a random integer in the given range (inclusive). */
+        virtual long randint(long start, long end) const = 0;
+
+        /*! Returns a random double in the given range (inclusive). */
+        virtual double randintDouble(double start, double end) const = 0;
+
+        /*! Returns a random integer in the given range (inclusive) except the given integer. */
+        virtual long randintExcept(long start, long end, long except) const = 0;
+};
+
+} // namespace libscratchcpp
diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt
index 506489de..d1b47737 100644
--- a/src/engine/CMakeLists.txt
+++ b/src/engine/CMakeLists.txt
@@ -20,7 +20,6 @@ target_sources(scratchcpp
     internal/stacktimer.h
     internal/randomgenerator.h
     internal/randomgenerator.cpp
-    internal/irandomgenerator.h
 )
 
 if(NOT LIBSCRATCHCPP_USE_LLVM)
diff --git a/src/engine/internal/irandomgenerator.h b/src/engine/internal/irandomgenerator.h
deleted file mode 100644
index 2228c933..00000000
--- a/src/engine/internal/irandomgenerator.h
+++ /dev/null
@@ -1,18 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-#pragma once
-
-namespace libscratchcpp
-{
-
-class IRandomGenerator
-{
-    public:
-        virtual ~IRandomGenerator() { }
-
-        virtual long randint(long start, long end) const = 0;
-        virtual double randintDouble(double start, double end) const = 0;
-        virtual long randintExcept(long start, long end, long except) const = 0;
-};
-
-} // namespace libscratchcpp
diff --git a/src/engine/internal/randomgenerator.h b/src/engine/internal/randomgenerator.h
index 1358de37..69d7cabd 100644
--- a/src/engine/internal/randomgenerator.h
+++ b/src/engine/internal/randomgenerator.h
@@ -2,11 +2,10 @@
 
 #pragma once
 
+#include <scratchcpp/irandomgenerator.h>
 #include <memory>
 #include <random>
 
-#include "irandomgenerator.h"
-
 namespace libscratchcpp
 {
 
diff --git a/test/mocks/randomgeneratormock.h b/test/mocks/randomgeneratormock.h
index 6e07f881..eea08335 100644
--- a/test/mocks/randomgeneratormock.h
+++ b/test/mocks/randomgeneratormock.h
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <engine/internal/irandomgenerator.h>
+#include <scratchcpp/irandomgenerator.h>
 #include <gmock/gmock.h>
 
 using namespace libscratchcpp;

From f782936efd9922a99cdb4ee528f1b7d0acde3c89 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 14:57:08 +0100
Subject: [PATCH 152/259] ExecutionContext: Add random number generator

---
 include/scratchcpp/dev/executioncontext.h           |  4 ++++
 src/dev/engine/executioncontext.cpp                 | 12 ++++++++++++
 src/dev/engine/executioncontext_p.cpp               |  4 +++-
 src/dev/engine/executioncontext_p.h                 |  2 ++
 test/dev/executioncontext/executioncontext_test.cpp | 11 +++++++++++
 5 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h
index 970041c0..69d11945 100644
--- a/include/scratchcpp/dev/executioncontext.h
+++ b/include/scratchcpp/dev/executioncontext.h
@@ -12,6 +12,7 @@ class Thread;
 class IEngine;
 class Promise;
 class IStackTimer;
+class IRandomGenerator;
 class ExecutionContextPrivate;
 
 /*! \brief The ExecutionContext represents the execution context of a target (can be a clone) with variables, lists, etc. */
@@ -31,6 +32,9 @@ class LIBSCRATCHCPP_EXPORT ExecutionContext
         IStackTimer *stackTimer() const;
         void setStackTimer(IStackTimer *newStackTimer);
 
+        IRandomGenerator *rng() const;
+        void setRng(IRandomGenerator *newRng);
+
     private:
         spimpl::unique_impl_ptr<ExecutionContextPrivate> impl;
 };
diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp
index 5c72cd54..fb0d9327 100644
--- a/src/dev/engine/executioncontext.cpp
+++ b/src/dev/engine/executioncontext.cpp
@@ -48,3 +48,15 @@ void ExecutionContext::setStackTimer(IStackTimer *newStackTimer)
 {
     impl->stackTimer = newStackTimer;
 }
+
+/*! Returns the random number generator of this context. */
+IRandomGenerator *ExecutionContext::rng() const
+{
+    return impl->rng;
+}
+
+/*! Sets a custom random number generator. */
+void ExecutionContext::setRng(IRandomGenerator *newRng)
+{
+    impl->rng = newRng;
+}
diff --git a/src/dev/engine/executioncontext_p.cpp b/src/dev/engine/executioncontext_p.cpp
index 03970aa7..94dd1072 100644
--- a/src/dev/engine/executioncontext_p.cpp
+++ b/src/dev/engine/executioncontext_p.cpp
@@ -1,12 +1,14 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include "executioncontext_p.h"
+#include "../../engine/internal/randomgenerator.h"
 
 using namespace libscratchcpp;
 
 ExecutionContextPrivate::ExecutionContextPrivate(Thread *thread) :
     thread(thread),
     defaultStackTimer(std::make_unique<StackTimer>()),
-    stackTimer(defaultStackTimer.get())
+    stackTimer(defaultStackTimer.get()),
+    rng(RandomGenerator::instance().get())
 {
 }
diff --git a/src/dev/engine/executioncontext_p.h b/src/dev/engine/executioncontext_p.h
index c556839d..367452aa 100644
--- a/src/dev/engine/executioncontext_p.h
+++ b/src/dev/engine/executioncontext_p.h
@@ -11,6 +11,7 @@ namespace libscratchcpp
 
 class Thread;
 class Promise;
+class IRandomGenerator;
 
 struct ExecutionContextPrivate
 {
@@ -20,6 +21,7 @@ struct ExecutionContextPrivate
         std::shared_ptr<Promise> promise;
         std::unique_ptr<IStackTimer> defaultStackTimer;
         IStackTimer *stackTimer = nullptr;
+        IRandomGenerator *rng = nullptr;
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/executioncontext/executioncontext_test.cpp b/test/dev/executioncontext/executioncontext_test.cpp
index 48504cd1..d6945813 100644
--- a/test/dev/executioncontext/executioncontext_test.cpp
+++ b/test/dev/executioncontext/executioncontext_test.cpp
@@ -4,6 +4,7 @@
 
 #include <enginemock.h>
 #include <stacktimermock.h>
+#include <randomgeneratormock.h>
 
 #include "../../common.h"
 
@@ -40,3 +41,13 @@ TEST(ExecutionContextTest, StackTimer)
     ctx.setStackTimer(&timer);
     ASSERT_EQ(ctx.stackTimer(), &timer);
 }
+
+TEST(ExecutionContextTest, Rng)
+{
+    ExecutionContext ctx(nullptr);
+    ASSERT_TRUE(ctx.rng());
+
+    RandomGeneratorMock rng;
+    ctx.setRng(&rng);
+    ASSERT_EQ(ctx.rng(), &rng);
+}

From 0ef6a2b4f8a41584d69590bdff5f32ad4314c734 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 14:57:42 +0100
Subject: [PATCH 153/259] LLVMCodeBuilder: Use random number generator from
 execution context

---
 src/dev/engine/internal/llvm/CMakeLists.txt   |  1 -
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 15 ++++++-----
 .../engine/internal/llvm/llvmfunctions.cpp    | 26 ++++++-------------
 src/dev/engine/internal/llvm/llvmfunctions.h  | 11 --------
 test/dev/llvm/llvmcodebuilder_test.cpp        |  7 ++---
 5 files changed, 19 insertions(+), 41 deletions(-)
 delete mode 100644 src/dev/engine/internal/llvm/llvmfunctions.h

diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index 7bd34c4d..315be55a 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -16,7 +16,6 @@ target_sources(scratchcpp
     llvmtypes.cpp
     llvmtypes.h
     llvmfunctions.cpp
-    llvmfunctions.h
     llvmexecutablecode.cpp
     llvmexecutablecode.h
     llvmexecutioncontext.cpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index c348b387..74698bf2 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -201,7 +201,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 if (reg1->type() == Compiler::StaticType::Bool && reg2->type() == Compiler::StaticType::Bool) {
                     llvm::Value *bool1 = castValue(arg1.second, Compiler::StaticType::Bool);
                     llvm::Value *bool2 = castValue(arg2.second, Compiler::StaticType::Bool);
-                    step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_bool(), { bool1, bool2 });
+                    step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_bool(), { executionContextPtr, bool1, bool2 });
                 } else {
                     llvm::Constant *inf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false);
                     llvm::Value *num1 = removeNaN(castValue(arg1.second, Compiler::StaticType::Number));
@@ -212,11 +212,11 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                     // NOTE: The random function will be called even in edge cases where it isn't needed, but they're rare, so it shouldn't be an issue
                     if (reg1->type() == Compiler::StaticType::Number && reg2->type() == Compiler::StaticType::Number)
-                        step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random_double(), { num1, num2 }));
+                        step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random_double(), { executionContextPtr, num1, num2 }));
                     else {
                         llvm::Value *value1 = createValue(reg1);
                         llvm::Value *value2 = createValue(reg2);
-                        step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random(), { value1, value2 }));
+                        step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random(), { executionContextPtr, value1, value2 }));
                     }
                 }
 
@@ -2373,18 +2373,21 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string()
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random()
 {
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
     llvm::Type *valuePtr = m_valueDataType->getPointerTo();
-    return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { valuePtr, valuePtr }, false));
+    return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, valuePtr, valuePtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double()
 {
-    return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false));
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool()
 {
-    return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false));
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
diff --git a/src/dev/engine/internal/llvm/llvmfunctions.cpp b/src/dev/engine/internal/llvm/llvmfunctions.cpp
index 8664ceb2..d07be391 100644
--- a/src/dev/engine/internal/llvm/llvmfunctions.cpp
+++ b/src/dev/engine/internal/llvm/llvmfunctions.cpp
@@ -1,37 +1,27 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include <scratchcpp/value_functions.h>
-
-#include "llvmfunctions.h"
-#include "../../../../engine/internal/randomgenerator.h"
+#include <scratchcpp/dev/executioncontext.h>
+#include <scratchcpp/irandomgenerator.h>
 
 namespace libscratchcpp
 {
 
 extern "C"
 {
-    double llvm_random(ValueData *from, ValueData *to)
+    double llvm_random(ExecutionContext *ctx, ValueData *from, ValueData *to)
     {
-        if (!llvm_rng)
-            llvm_rng = RandomGenerator::instance().get();
-
-        return value_isInt(from) && value_isInt(to) ? llvm_rng->randint(value_toLong(from), value_toLong(to)) : llvm_rng->randintDouble(value_toDouble(from), value_toDouble(to));
+        return value_isInt(from) && value_isInt(to) ? ctx->rng()->randint(value_toLong(from), value_toLong(to)) : ctx->rng()->randintDouble(value_toDouble(from), value_toDouble(to));
     }
 
-    double llvm_random_double(double from, double to)
+    double llvm_random_double(ExecutionContext *ctx, double from, double to)
     {
-        if (!llvm_rng)
-            llvm_rng = RandomGenerator::instance().get();
-
-        return value_doubleIsInt(from) && value_doubleIsInt(to) ? llvm_rng->randint(from, to) : llvm_rng->randintDouble(from, to);
+        return value_doubleIsInt(from) && value_doubleIsInt(to) ? ctx->rng()->randint(from, to) : ctx->rng()->randintDouble(from, to);
     }
 
-    double llvm_random_bool(bool from, bool to)
+    double llvm_random_bool(ExecutionContext *ctx, bool from, bool to)
     {
-        if (!llvm_rng)
-            llvm_rng = RandomGenerator::instance().get();
-
-        return llvm_rng->randint(from, to);
+        return ctx->rng()->randint(from, to);
     }
 }
 
diff --git a/src/dev/engine/internal/llvm/llvmfunctions.h b/src/dev/engine/internal/llvm/llvmfunctions.h
deleted file mode 100644
index cf078872..00000000
--- a/src/dev/engine/internal/llvm/llvmfunctions.h
+++ /dev/null
@@ -1,11 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-#pragma once
-
-namespace libscratchcpp
-{
-
-class IRandomGenerator;
-
-IRandomGenerator *llvm_rng = nullptr;
-
-} // namespace libscratchcpp
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index a67dd965..fa631be4 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1,5 +1,6 @@
 #include <scratchcpp/value.h>
 #include <scratchcpp/dev/executablecode.h>
+#include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/script.h>
 #include <scratchcpp/thread.h>
 #include <scratchcpp/sprite.h>
@@ -7,7 +8,6 @@
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
 #include <dev/engine/internal/llvm/llvmcodebuilder.h>
-#include <dev/engine/internal/llvm/llvmfunctions.h>
 #include <gmock/gmock.h>
 #include <targetmock.h>
 #include <enginemock.h>
@@ -260,6 +260,7 @@ class LLVMCodeBuilderTest : public testing::Test
             script.setCode(code);
             Thread thread(&m_target, nullptr, &script);
             auto ctx = code->createExecutionContext(&thread);
+            ctx->setRng(&m_rng);
 
             testing::internal::CaptureStdout();
             code->run(ctx.get());
@@ -638,8 +639,6 @@ TEST_F(LLVMCodeBuilderTest, Divide)
 
 TEST_F(LLVMCodeBuilderTest, Random)
 {
-    llvm_rng = &m_rng;
-
     EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18));
     runOpTest(OpType::Random, -45, 12);
 
@@ -716,8 +715,6 @@ TEST_F(LLVMCodeBuilderTest, Random)
     runOpTest(OpType::Random, -inf, -inf, -inf);
     runOpTest(OpType::Random, inf, -inf, nan);
     runOpTest(OpType::Random, -inf, inf, nan);
-
-    llvm_rng = nullptr;
 }
 
 TEST_F(LLVMCodeBuilderTest, EqualComparison)

From a83dae74f9747f9039d870d9ea122bd7f7c903bf Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 15:49:37 +0100
Subject: [PATCH 154/259] Compiler: Add createRandom() method

---
 include/scratchcpp/dev/compiler.h   |  2 ++
 src/dev/engine/compiler.cpp         |  6 ++++++
 test/dev/compiler/compiler_test.cpp | 18 ++++++++++++++++++
 3 files changed, 26 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index b7f5cc73..eb70df4d 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -65,6 +65,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2);
 
+        CompilerValue *createRandom(CompilerValue *from, CompilerValue *to);
+
         CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 0c633c72..807fa11f 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -193,6 +193,12 @@ CompilerValue *Compiler::createDiv(CompilerValue *operand1, CompilerValue *opera
     return impl->builder->createDiv(operand1, operand2);
 }
 
+/*! Creates a random instruction (Scratch behavior). */
+CompilerValue *Compiler::createRandom(CompilerValue *from, CompilerValue *to)
+{
+    return impl->builder->createRandom(from, to);
+}
+
 /*! Creates an equality comparison instruction. */
 CompilerValue *Compiler::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index cacd80b5..c2abc8d2 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -466,6 +466,24 @@ TEST_F(CompilerTest, CreateDiv)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CreateRandom)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createRandom(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createRandom(&arg1, &arg2), &ret);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, CreateCmpEQ)
 {
     Compiler compiler(&m_engine, &m_target);

From b678de0a86562f64a3f1bb3a4aa0e6abc83f4fbd Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 15:50:09 +0100
Subject: [PATCH 155/259] Implement operator_random

---
 src/dev/blocks/operatorblocks.cpp        |  9 ++++
 src/dev/blocks/operatorblocks.h          |  3 ++
 test/dev/blocks/CMakeLists.txt           |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 61 ++++++++++++++++++++++++
 4 files changed, 74 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index f98a924b..d7523475 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -2,6 +2,7 @@
 
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilervalue.h>
 
 #include "operatorblocks.h"
 
@@ -23,6 +24,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_subtract", &compileSubtract);
     engine->addCompileFunction(this, "operator_multiply", &compileMultiply);
     engine->addCompileFunction(this, "operator_divide", &compileDivide);
+    engine->addCompileFunction(this, "operator_random", &compileRandom);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -44,3 +46,10 @@ CompilerValue *OperatorBlocks::compileDivide(Compiler *compiler)
 {
     return compiler->createDiv(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
 }
+
+CompilerValue *OperatorBlocks::compileRandom(Compiler *compiler)
+{
+    auto from = compiler->addInput("FROM");
+    auto to = compiler->addInput("TO");
+    return compiler->createRandom(from, to);
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 89b7a1d6..5f1c9aba 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -7,6 +7,8 @@
 namespace libscratchcpp
 {
 
+class IRandomGenerator;
+
 class OperatorBlocks : public IExtension
 {
     public:
@@ -20,6 +22,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileSubtract(Compiler *compiler);
         static CompilerValue *compileMultiply(Compiler *compiler);
         static CompilerValue *compileDivide(Compiler *compiler);
+        static CompilerValue *compileRandom(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/CMakeLists.txt b/test/dev/blocks/CMakeLists.txt
index 3fc113f7..c36e43cf 100644
--- a/test/dev/blocks/CMakeLists.txt
+++ b/test/dev/blocks/CMakeLists.txt
@@ -132,6 +132,7 @@ if (LIBSCRATCHCPP_ENABLE_OPERATOR_BLOCKS)
       GTest::gmock_main
       scratchcpp
       scratchcpp_mocks
+      block_test_deps
     )
 
     gtest_discover_tests(operator_blocks_test)
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 13282adb..f77041d8 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -2,15 +2,25 @@
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/list.h>
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/script.h>
+#include <scratchcpp/thread.h>
+#include <scratchcpp/dev/executablecode.h>
+#include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/dev/test/scriptbuilder.h>
 #include <enginemock.h>
+#include <randomgeneratormock.h>
 
 #include "../common.h"
 #include "dev/blocks/operatorblocks.h"
+#include "util.h"
 
 using namespace libscratchcpp;
 using namespace libscratchcpp::test;
 
+using ::testing::Return;
+
 class OperatorBlocksTest : public testing::Test
 {
     public:
@@ -19,12 +29,14 @@ class OperatorBlocksTest : public testing::Test
             m_extension = std::make_unique<OperatorBlocks>();
             m_engine = m_project.engine().get();
             m_extension->registerBlocks(m_engine);
+            registerBlocks(m_engine, m_extension.get());
         }
 
         std::unique_ptr<IExtension> m_extension;
         Project m_project;
         IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
+        RandomGeneratorMock m_rng;
 };
 
 TEST_F(OperatorBlocksTest, Add)
@@ -102,3 +114,52 @@ TEST_F(OperatorBlocksTest, Divide)
     ASSERT_EQ(valueList->size(), 1);
     ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 2.28);
 }
+
+TEST_F(OperatorBlocksTest, Random)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addRandomTest = [&builder](const Value &from, const Value &to) {
+        auto block = std::make_shared<Block>("", "operator_random");
+        auto input = std::make_shared<Input>("FROM", Input::Type::Shadow);
+        input->setPrimaryValue(from);
+        block->addInput(input);
+        input = std::make_shared<Input>("TO", Input::Type::Shadow);
+        input->setPrimaryValue(to);
+        block->addInput(input);
+
+        builder.addBlock("test_print");
+        builder.addObscuredInput("STRING", block);
+        return builder.currentBlock();
+    };
+
+    auto block = addRandomTest(-45, 12);
+    addRandomTest(12, 6.05);
+    addRandomTest(-78.686, -45);
+    addRandomTest(6.05, -78.686);
+
+    builder.build();
+
+    Compiler compiler(&m_engineMock, target.get());
+    auto code = compiler.compile(block);
+    Script script(target.get(), block, &m_engineMock);
+    script.setCode(code);
+    Thread thread(target.get(), &m_engineMock, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    ctx->setRng(&m_rng);
+
+    static const std::string expected =
+        "-18\n"
+        "3.486789\n"
+        "-59.468873\n"
+        "-28.648764\n";
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).WillOnce(Return(-18));
+    EXPECT_CALL(m_rng, randintDouble(12, 6.05)).WillOnce(Return(3.486789));
+    EXPECT_CALL(m_rng, randintDouble(-78.686, -45)).WillOnce(Return(-59.468873));
+    EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).WillOnce(Return(-28.648764));
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}

From 3a39a57ac47680885c34e1636fd294229f9d88ce Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 16:02:27 +0100
Subject: [PATCH 156/259] Implement operator_lt

---
 src/dev/blocks/operatorblocks.cpp        |  6 +++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 31 ++++++++++++++++++++++++
 3 files changed, 38 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index d7523475..c9f8937b 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -25,6 +25,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_multiply", &compileMultiply);
     engine->addCompileFunction(this, "operator_divide", &compileDivide);
     engine->addCompileFunction(this, "operator_random", &compileRandom);
+    engine->addCompileFunction(this, "operator_lt", &compileLt);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -53,3 +54,8 @@ CompilerValue *OperatorBlocks::compileRandom(Compiler *compiler)
     auto to = compiler->addInput("TO");
     return compiler->createRandom(from, to);
 }
+
+CompilerValue *OperatorBlocks::compileLt(Compiler *compiler)
+{
+    return compiler->createCmpLT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 5f1c9aba..5eccac85 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -23,6 +23,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileMultiply(Compiler *compiler);
         static CompilerValue *compileDivide(Compiler *compiler);
         static CompilerValue *compileRandom(Compiler *compiler);
+        static CompilerValue *compileLt(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index f77041d8..f89f04b8 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -163,3 +163,34 @@ TEST_F(OperatorBlocksTest, Random)
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
+
+TEST_F(OperatorBlocksTest, Lt)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_lt");
+    builder.addValueInput("OPERAND1", 5.4645);
+    builder.addValueInput("OPERAND2", 12.486);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_lt");
+    builder.addValueInput("OPERAND1", 153.25);
+    builder.addValueInput("OPERAND2", 96.5);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_lt");
+    builder.addValueInput("OPERAND1", 2.8465);
+    builder.addValueInput("OPERAND2", 2.8465);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 3);
+    ASSERT_EQ(Value(values[0]), true);
+    ASSERT_EQ(Value(values[1]), false);
+    ASSERT_EQ(Value(values[2]), false);
+}

From c040d5ef70835b679255e52868a1be38385e2c3e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 16:07:01 +0100
Subject: [PATCH 157/259] Implement operator_equals

---
 src/dev/blocks/operatorblocks.cpp        |  6 +++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 31 ++++++++++++++++++++++++
 3 files changed, 38 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index c9f8937b..1ed1f0ff 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -26,6 +26,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_divide", &compileDivide);
     engine->addCompileFunction(this, "operator_random", &compileRandom);
     engine->addCompileFunction(this, "operator_lt", &compileLt);
+    engine->addCompileFunction(this, "operator_equals", &compileEquals);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -59,3 +60,8 @@ CompilerValue *OperatorBlocks::compileLt(Compiler *compiler)
 {
     return compiler->createCmpLT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
 }
+
+CompilerValue *OperatorBlocks::compileEquals(Compiler *compiler)
+{
+    return compiler->createCmpEQ(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 5eccac85..e1c46064 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -24,6 +24,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileDivide(Compiler *compiler);
         static CompilerValue *compileRandom(Compiler *compiler);
         static CompilerValue *compileLt(Compiler *compiler);
+        static CompilerValue *compileEquals(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index f89f04b8..d7812863 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -194,3 +194,34 @@ TEST_F(OperatorBlocksTest, Lt)
     ASSERT_EQ(Value(values[1]), false);
     ASSERT_EQ(Value(values[2]), false);
 }
+
+TEST_F(OperatorBlocksTest, Equals)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_equals");
+    builder.addValueInput("OPERAND1", 5.4645);
+    builder.addValueInput("OPERAND2", 12.486);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_equals");
+    builder.addValueInput("OPERAND1", 153.25);
+    builder.addValueInput("OPERAND2", 96.5);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_equals");
+    builder.addValueInput("OPERAND1", 2.8465);
+    builder.addValueInput("OPERAND2", 2.8465);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 3);
+    ASSERT_EQ(Value(values[0]), false);
+    ASSERT_EQ(Value(values[1]), false);
+    ASSERT_EQ(Value(values[2]), true);
+}

From d3ef6a1144d0b5b95631a99f89d5361eb2c38d8c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 16:08:02 +0100
Subject: [PATCH 158/259] Implement operator_gt

---
 src/dev/blocks/operatorblocks.cpp        |  6 +++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 31 ++++++++++++++++++++++++
 3 files changed, 38 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 1ed1f0ff..9a180e1c 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -27,6 +27,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_random", &compileRandom);
     engine->addCompileFunction(this, "operator_lt", &compileLt);
     engine->addCompileFunction(this, "operator_equals", &compileEquals);
+    engine->addCompileFunction(this, "operator_gt", &compileGt);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -65,3 +66,8 @@ CompilerValue *OperatorBlocks::compileEquals(Compiler *compiler)
 {
     return compiler->createCmpEQ(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
 }
+
+CompilerValue *OperatorBlocks::compileGt(Compiler *compiler)
+{
+    return compiler->createCmpGT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index e1c46064..21093bdd 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -25,6 +25,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileRandom(Compiler *compiler);
         static CompilerValue *compileLt(Compiler *compiler);
         static CompilerValue *compileEquals(Compiler *compiler);
+        static CompilerValue *compileGt(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index d7812863..aa1ba51c 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -225,3 +225,34 @@ TEST_F(OperatorBlocksTest, Equals)
     ASSERT_EQ(Value(values[1]), false);
     ASSERT_EQ(Value(values[2]), true);
 }
+
+TEST_F(OperatorBlocksTest, Gt)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_gt");
+    builder.addValueInput("OPERAND1", 5.4645);
+    builder.addValueInput("OPERAND2", 12.486);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_gt");
+    builder.addValueInput("OPERAND1", 153.25);
+    builder.addValueInput("OPERAND2", 96.5);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_gt");
+    builder.addValueInput("OPERAND1", 2.8465);
+    builder.addValueInput("OPERAND2", 2.8465);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 3);
+    ASSERT_EQ(Value(values[0]), false);
+    ASSERT_EQ(Value(values[1]), true);
+    ASSERT_EQ(Value(values[2]), false);
+}

From 1ce8e6fb6a1661eff9d724e91bdf10544be5c197 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 16:14:30 +0100
Subject: [PATCH 159/259] Implement operator_and

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 37 ++++++++++++++++++++++++
 3 files changed, 44 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 9a180e1c..7198eec3 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -28,6 +28,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_lt", &compileLt);
     engine->addCompileFunction(this, "operator_equals", &compileEquals);
     engine->addCompileFunction(this, "operator_gt", &compileGt);
+    engine->addCompileFunction(this, "operator_and", &compileAnd);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -71,3 +72,8 @@ CompilerValue *OperatorBlocks::compileGt(Compiler *compiler)
 {
     return compiler->createCmpGT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
 }
+
+CompilerValue *OperatorBlocks::compileAnd(Compiler *compiler)
+{
+    return compiler->createAnd(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 21093bdd..4c8bcc91 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -26,6 +26,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileLt(Compiler *compiler);
         static CompilerValue *compileEquals(Compiler *compiler);
         static CompilerValue *compileGt(Compiler *compiler);
+        static CompilerValue *compileAnd(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index aa1ba51c..d60a81ea 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -256,3 +256,40 @@ TEST_F(OperatorBlocksTest, Gt)
     ASSERT_EQ(Value(values[1]), true);
     ASSERT_EQ(Value(values[2]), false);
 }
+
+TEST_F(OperatorBlocksTest, And)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_and");
+    builder.addValueInput("OPERAND1", false);
+    builder.addValueInput("OPERAND2", false);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_and");
+    builder.addValueInput("OPERAND1", true);
+    builder.addValueInput("OPERAND2", false);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_and");
+    builder.addValueInput("OPERAND1", false);
+    builder.addValueInput("OPERAND2", true);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_and");
+    builder.addValueInput("OPERAND1", true);
+    builder.addValueInput("OPERAND2", true);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 4);
+    ASSERT_EQ(Value(values[0]), false);
+    ASSERT_EQ(Value(values[1]), false);
+    ASSERT_EQ(Value(values[2]), false);
+    ASSERT_EQ(Value(values[3]), true);
+}

From 6e1db6e93d3c51a30b7cf230408015f845207993 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 16:16:53 +0100
Subject: [PATCH 160/259] Implement operator_or

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 37 ++++++++++++++++++++++++
 3 files changed, 44 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 7198eec3..7a03b9f3 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -29,6 +29,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_equals", &compileEquals);
     engine->addCompileFunction(this, "operator_gt", &compileGt);
     engine->addCompileFunction(this, "operator_and", &compileAnd);
+    engine->addCompileFunction(this, "operator_or", &compileOr);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -77,3 +78,8 @@ CompilerValue *OperatorBlocks::compileAnd(Compiler *compiler)
 {
     return compiler->createAnd(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
 }
+
+CompilerValue *OperatorBlocks::compileOr(Compiler *compiler)
+{
+    return compiler->createOr(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 4c8bcc91..28af276e 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -27,6 +27,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileEquals(Compiler *compiler);
         static CompilerValue *compileGt(Compiler *compiler);
         static CompilerValue *compileAnd(Compiler *compiler);
+        static CompilerValue *compileOr(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index d60a81ea..80bdfa8f 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -293,3 +293,40 @@ TEST_F(OperatorBlocksTest, And)
     ASSERT_EQ(Value(values[2]), false);
     ASSERT_EQ(Value(values[3]), true);
 }
+
+TEST_F(OperatorBlocksTest, Or)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_or");
+    builder.addValueInput("OPERAND1", false);
+    builder.addValueInput("OPERAND2", false);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_or");
+    builder.addValueInput("OPERAND1", true);
+    builder.addValueInput("OPERAND2", false);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_or");
+    builder.addValueInput("OPERAND1", false);
+    builder.addValueInput("OPERAND2", true);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_or");
+    builder.addValueInput("OPERAND1", true);
+    builder.addValueInput("OPERAND2", true);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 4);
+    ASSERT_EQ(Value(values[0]), false);
+    ASSERT_EQ(Value(values[1]), true);
+    ASSERT_EQ(Value(values[2]), true);
+    ASSERT_EQ(Value(values[3]), true);
+}

From 4ee628ad23b614e94b2791a4cbfeec9c91e856bb Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 16:22:04 +0100
Subject: [PATCH 161/259] Implement operator_not

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 23 +++++++++++++++++++++++
 3 files changed, 30 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 7a03b9f3..ebf4e1f8 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -30,6 +30,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_gt", &compileGt);
     engine->addCompileFunction(this, "operator_and", &compileAnd);
     engine->addCompileFunction(this, "operator_or", &compileOr);
+    engine->addCompileFunction(this, "operator_not", &compileNot);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -83,3 +84,8 @@ CompilerValue *OperatorBlocks::compileOr(Compiler *compiler)
 {
     return compiler->createOr(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2"));
 }
+
+CompilerValue *OperatorBlocks::compileNot(Compiler *compiler)
+{
+    return compiler->createNot(compiler->addInput("OPERAND"));
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 28af276e..8570225c 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -28,6 +28,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileGt(Compiler *compiler);
         static CompilerValue *compileAnd(Compiler *compiler);
         static CompilerValue *compileOr(Compiler *compiler);
+        static CompilerValue *compileNot(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 80bdfa8f..2993177e 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -330,3 +330,26 @@ TEST_F(OperatorBlocksTest, Or)
     ASSERT_EQ(Value(values[2]), true);
     ASSERT_EQ(Value(values[3]), true);
 }
+
+TEST_F(OperatorBlocksTest, Not)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_not");
+    builder.addValueInput("OPERAND", false);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_not");
+    builder.addValueInput("OPERAND", true);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 2);
+    ASSERT_EQ(Value(values[0]), true);
+    ASSERT_EQ(Value(values[1]), false);
+}

From f66699f2d69270cb8da2749875d772e66a8e1bc1 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 16:40:43 +0100
Subject: [PATCH 162/259] Implement operator_join

---
 src/dev/blocks/operatorblocks.cpp        | 25 ++++++++++++++++++++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 25 ++++++++++++++++++++++++
 3 files changed, 51 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index ebf4e1f8..45bb0f75 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -31,6 +31,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_and", &compileAnd);
     engine->addCompileFunction(this, "operator_or", &compileOr);
     engine->addCompileFunction(this, "operator_not", &compileNot);
+    engine->addCompileFunction(this, "operator_join", &compileJoin);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -89,3 +90,27 @@ CompilerValue *OperatorBlocks::compileNot(Compiler *compiler)
 {
     return compiler->createNot(compiler->addInput("OPERAND"));
 }
+
+CompilerValue *OperatorBlocks::compileJoin(Compiler *compiler)
+{
+    auto string1 = compiler->addInput("STRING1");
+    auto string2 = compiler->addInput("STRING2");
+    return compiler->addFunctionCall("operator_join", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 });
+}
+
+extern "C" char *operator_join(const char *string1, const char *string2)
+{
+    const size_t len1 = strlen(string1);
+    const size_t len2 = strlen(string2);
+
+    char *ret = (char *)malloc((len1 + len2 + 1) * sizeof(char));
+    size_t i;
+
+    for (i = 0; i < len1; i++)
+        ret[i] = string1[i];
+
+    for (i = 0; i < len2 + 1; i++) // +1: null-terminate
+        ret[len1 + i] = string2[i];
+
+    return ret;
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 8570225c..7adadad2 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -29,6 +29,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileAnd(Compiler *compiler);
         static CompilerValue *compileOr(Compiler *compiler);
         static CompilerValue *compileNot(Compiler *compiler);
+        static CompilerValue *compileJoin(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 2993177e..295cd18a 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -353,3 +353,28 @@ TEST_F(OperatorBlocksTest, Not)
     ASSERT_EQ(Value(values[0]), true);
     ASSERT_EQ(Value(values[1]), false);
 }
+
+TEST_F(OperatorBlocksTest, Join)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_join");
+    builder.addValueInput("STRING1", "abc");
+    builder.addValueInput("STRING2", "def");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_join");
+    builder.addValueInput("STRING1", "Hello ");
+    builder.addValueInput("STRING2", "world");
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 2);
+    ASSERT_EQ(Value(values[0]), "abcdef");
+    ASSERT_EQ(Value(values[1]), "Hello world");
+}

From 676cc834ce6e7a8c0da9fb45c2ad69d237d16f14 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 23 Dec 2024 17:03:45 +0100
Subject: [PATCH 163/259] Implement operator_letter_of

---
 src/dev/blocks/operatorblocks.cpp        | 28 +++++++++++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 43 ++++++++++++++++++++++++
 3 files changed, 72 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 45bb0f75..4edea1d9 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -3,6 +3,7 @@
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/dev/compiler.h>
 #include <scratchcpp/dev/compilervalue.h>
+#include <utf8.h>
 
 #include "operatorblocks.h"
 
@@ -32,6 +33,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_or", &compileOr);
     engine->addCompileFunction(this, "operator_not", &compileNot);
     engine->addCompileFunction(this, "operator_join", &compileJoin);
+    engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -98,6 +100,13 @@ CompilerValue *OperatorBlocks::compileJoin(Compiler *compiler)
     return compiler->addFunctionCall("operator_join", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 });
 }
 
+CompilerValue *OperatorBlocks::compileLetterOf(Compiler *compiler)
+{
+    auto letter = compiler->addInput("LETTER");
+    auto string = compiler->addInput("STRING");
+    return compiler->addFunctionCall("operator_letter_of", Compiler::StaticType::String, { Compiler::StaticType::Number, Compiler::StaticType::String }, { letter, string });
+}
+
 extern "C" char *operator_join(const char *string1, const char *string2)
 {
     const size_t len1 = strlen(string1);
@@ -114,3 +123,22 @@ extern "C" char *operator_join(const char *string1, const char *string2)
 
     return ret;
 }
+
+extern "C" char *operator_letter_of(double letter, const char *string)
+{
+    const size_t len = strlen(string);
+
+    if (letter < 1 || letter > len) {
+        char *ret = (char *)malloc(sizeof(char));
+        ret[0] = '\0';
+        return ret;
+    }
+
+    // TODO: Rewrite this
+    std::u16string u16 = utf8::utf8to16(std::string(string));
+    std::string str = utf8::utf16to8(std::u16string({ u16[(size_t)letter - 1] }));
+    char *ret = (char *)malloc((str.size() + 1) * sizeof(char));
+    strcpy(ret, str.c_str());
+
+    return ret;
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 7adadad2..eef1b5b4 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -30,6 +30,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileOr(Compiler *compiler);
         static CompilerValue *compileNot(Compiler *compiler);
         static CompilerValue *compileJoin(Compiler *compiler);
+        static CompilerValue *compileLetterOf(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 295cd18a..d598bda8 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -378,3 +378,46 @@ TEST_F(OperatorBlocksTest, Join)
     ASSERT_EQ(Value(values[0]), "abcdef");
     ASSERT_EQ(Value(values[1]), "Hello world");
 }
+
+TEST_F(OperatorBlocksTest, LetterOf)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_letter_of");
+    builder.addValueInput("LETTER", 2);
+    builder.addValueInput("STRING", "abc");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_letter_of");
+    builder.addValueInput("LETTER", 7);
+    builder.addValueInput("STRING", "Hello world");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_letter_of");
+    builder.addValueInput("LETTER", 0);
+    builder.addValueInput("STRING", "Hello world");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_letter_of");
+    builder.addValueInput("LETTER", 12);
+    builder.addValueInput("STRING", "Hello world");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_letter_of");
+    builder.addValueInput("LETTER", 1);
+    builder.addValueInput("STRING", "Ábč");
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 5);
+    ASSERT_EQ(Value(values[0]), "b");
+    ASSERT_EQ(Value(values[1]), "w");
+    ASSERT_EQ(Value(values[2]), "");
+    ASSERT_EQ(Value(values[3]), "");
+    ASSERT_EQ(Value(values[4]), "Á");
+}

From 84d109cf25f8b01725cd0dcc1661ffc82c276a31 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 24 Dec 2024 00:01:08 +0100
Subject: [PATCH 164/259] Implement operator_length

---
 src/dev/blocks/operatorblocks.cpp        | 13 +++++++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 28 ++++++++++++++++++++++++
 3 files changed, 42 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 4edea1d9..fb2dfcdb 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -34,6 +34,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_not", &compileNot);
     engine->addCompileFunction(this, "operator_join", &compileJoin);
     engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf);
+    engine->addCompileFunction(this, "operator_length", &compileLength);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -107,6 +108,12 @@ CompilerValue *OperatorBlocks::compileLetterOf(Compiler *compiler)
     return compiler->addFunctionCall("operator_letter_of", Compiler::StaticType::String, { Compiler::StaticType::Number, Compiler::StaticType::String }, { letter, string });
 }
 
+CompilerValue *OperatorBlocks::compileLength(Compiler *compiler)
+{
+    auto string = compiler->addInput("STRING");
+    return compiler->addFunctionCall("operator_length", Compiler::StaticType::Number, { Compiler::StaticType::String }, { string });
+}
+
 extern "C" char *operator_join(const char *string1, const char *string2)
 {
     const size_t len1 = strlen(string1);
@@ -142,3 +149,9 @@ extern "C" char *operator_letter_of(double letter, const char *string)
 
     return ret;
 }
+
+extern "C" double operator_length(const char *string)
+{
+    // TODO: Rewrite this
+    return utf8::utf8to16(std::string(string)).size();
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index eef1b5b4..bd72e23f 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -31,6 +31,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileNot(Compiler *compiler);
         static CompilerValue *compileJoin(Compiler *compiler);
         static CompilerValue *compileLetterOf(Compiler *compiler);
+        static CompilerValue *compileLength(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index d598bda8..40d482f0 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -421,3 +421,31 @@ TEST_F(OperatorBlocksTest, LetterOf)
     ASSERT_EQ(Value(values[3]), "");
     ASSERT_EQ(Value(values[4]), "Á");
 }
+
+TEST_F(OperatorBlocksTest, Length)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_length");
+    builder.addValueInput("STRING", "abc");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_length");
+    builder.addValueInput("STRING", "Hello world");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_length");
+    builder.addValueInput("STRING", "dOádčĐaší");
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 3);
+    ASSERT_EQ(Value(values[0]), 3);
+    ASSERT_EQ(Value(values[1]), 11);
+    ASSERT_EQ(Value(values[2]), 9);
+}

From 1454aa556201b0b7bca3968ef09f25c2e6d898cf Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 24 Dec 2024 00:16:20 +0100
Subject: [PATCH 165/259] Implement operator_contains

---
 src/dev/blocks/operatorblocks.cpp        | 18 +++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 61 ++++++++++++++++++++++++
 3 files changed, 80 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index fb2dfcdb..eae9a5d8 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -35,6 +35,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_join", &compileJoin);
     engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf);
     engine->addCompileFunction(this, "operator_length", &compileLength);
+    engine->addCompileFunction(this, "operator_contains", &compileContains);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -114,6 +115,13 @@ CompilerValue *OperatorBlocks::compileLength(Compiler *compiler)
     return compiler->addFunctionCall("operator_length", Compiler::StaticType::Number, { Compiler::StaticType::String }, { string });
 }
 
+CompilerValue *OperatorBlocks::compileContains(Compiler *compiler)
+{
+    auto string1 = compiler->addInput("STRING1");
+    auto string2 = compiler->addInput("STRING2");
+    return compiler->addFunctionCall("operator_contains", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 });
+}
+
 extern "C" char *operator_join(const char *string1, const char *string2)
 {
     const size_t len1 = strlen(string1);
@@ -155,3 +163,13 @@ extern "C" double operator_length(const char *string)
     // TODO: Rewrite this
     return utf8::utf8to16(std::string(string)).size();
 }
+
+extern "C" bool operator_contains(const char *string1, const char *string2)
+{
+    // TODO: Rewrite this
+    std::u16string u16string1 = utf8::utf8to16(std::string(string1));
+    std::u16string u16string2 = utf8::utf8to16(std::string(string2));
+    std::transform(u16string1.begin(), u16string1.end(), u16string1.begin(), ::tolower);
+    std::transform(u16string2.begin(), u16string2.end(), u16string2.begin(), ::tolower);
+    return (u16string1.find(u16string2) != std::u16string::npos);
+}
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index bd72e23f..16dbfa6e 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -32,6 +32,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileJoin(Compiler *compiler);
         static CompilerValue *compileLetterOf(Compiler *compiler);
         static CompilerValue *compileLength(Compiler *compiler);
+        static CompilerValue *compileContains(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 40d482f0..d0290f4a 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -449,3 +449,64 @@ TEST_F(OperatorBlocksTest, Length)
     ASSERT_EQ(Value(values[1]), 11);
     ASSERT_EQ(Value(values[2]), 9);
 }
+
+TEST_F(OperatorBlocksTest, Contains)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "abc");
+    builder.addValueInput("STRING2", "a");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "abc");
+    builder.addValueInput("STRING2", "e");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "abc");
+    builder.addValueInput("STRING2", "C");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "Hello world");
+    builder.addValueInput("STRING2", "ello");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "Hello world");
+    builder.addValueInput("STRING2", "olld");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "ábČ");
+    builder.addValueInput("STRING2", "á");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "ábČ");
+    builder.addValueInput("STRING2", "bČ");
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_contains");
+    builder.addValueInput("STRING1", "ábČ");
+    builder.addValueInput("STRING2", "ďá");
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 8);
+    ASSERT_EQ(Value(values[0]), true);
+    ASSERT_EQ(Value(values[1]), false);
+    ASSERT_EQ(Value(values[2]), true);
+    ASSERT_EQ(Value(values[3]), true);
+    ASSERT_EQ(Value(values[4]), false);
+    ASSERT_EQ(Value(values[5]), true);
+    ASSERT_EQ(Value(values[6]), true);
+    ASSERT_EQ(Value(values[7]), false);
+}

From e237c7be5f48631ba68a405f89828971709d4dfc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 24 Dec 2024 11:27:01 +0100
Subject: [PATCH 166/259] Implement operator_mod

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 25 ++++++++++++++++++++++++
 3 files changed, 32 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index eae9a5d8..fc2afb1a 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -36,6 +36,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf);
     engine->addCompileFunction(this, "operator_length", &compileLength);
     engine->addCompileFunction(this, "operator_contains", &compileContains);
+    engine->addCompileFunction(this, "operator_mod", &compileMod);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -122,6 +123,11 @@ CompilerValue *OperatorBlocks::compileContains(Compiler *compiler)
     return compiler->addFunctionCall("operator_contains", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 });
 }
 
+CompilerValue *OperatorBlocks::compileMod(Compiler *compiler)
+{
+    return compiler->createMod(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
+}
+
 extern "C" char *operator_join(const char *string1, const char *string2)
 {
     const size_t len1 = strlen(string1);
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 16dbfa6e..3a365418 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -33,6 +33,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileLetterOf(Compiler *compiler);
         static CompilerValue *compileLength(Compiler *compiler);
         static CompilerValue *compileContains(Compiler *compiler);
+        static CompilerValue *compileMod(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index d0290f4a..277b38da 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -510,3 +510,28 @@ TEST_F(OperatorBlocksTest, Contains)
     ASSERT_EQ(Value(values[6]), true);
     ASSERT_EQ(Value(values[7]), false);
 }
+
+TEST_F(OperatorBlocksTest, Mod)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_mod");
+    builder.addValueInput("NUM1", 5.7);
+    builder.addValueInput("NUM2", 2.5);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mod");
+    builder.addValueInput("NUM1", -5.7);
+    builder.addValueInput("NUM2", 2.5);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 2);
+    ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 0.7);
+    ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 1.8);
+}

From 5ef36d0a175737bd7903c88be638574472d0c466 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 24 Dec 2024 11:31:37 +0100
Subject: [PATCH 167/259] Implement operator_round

---
 src/dev/blocks/operatorblocks.cpp        |  6 ++++++
 src/dev/blocks/operatorblocks.h          |  1 +
 test/dev/blocks/operator_blocks_test.cpp | 23 +++++++++++++++++++++++
 3 files changed, 30 insertions(+)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index fc2afb1a..3cbd042a 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -37,6 +37,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_length", &compileLength);
     engine->addCompileFunction(this, "operator_contains", &compileContains);
     engine->addCompileFunction(this, "operator_mod", &compileMod);
+    engine->addCompileFunction(this, "operator_round", &compileRound);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -128,6 +129,11 @@ CompilerValue *OperatorBlocks::compileMod(Compiler *compiler)
     return compiler->createMod(compiler->addInput("NUM1"), compiler->addInput("NUM2"));
 }
 
+CompilerValue *OperatorBlocks::compileRound(Compiler *compiler)
+{
+    return compiler->createRound(compiler->addInput("NUM"));
+}
+
 extern "C" char *operator_join(const char *string1, const char *string2)
 {
     const size_t len1 = strlen(string1);
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 3a365418..be39423c 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -34,6 +34,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileLength(Compiler *compiler);
         static CompilerValue *compileContains(Compiler *compiler);
         static CompilerValue *compileMod(Compiler *compiler);
+        static CompilerValue *compileRound(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 277b38da..0947d46d 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -535,3 +535,26 @@ TEST_F(OperatorBlocksTest, Mod)
     ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 0.7);
     ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 1.8);
 }
+
+TEST_F(OperatorBlocksTest, Round)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("operator_round");
+    builder.addValueInput("NUM", 5.7);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_round");
+    builder.addValueInput("NUM", 2.3);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 2);
+    ASSERT_EQ(Value(values[0]), 6);
+    ASSERT_EQ(Value(values[1]), 2);
+}

From 833727c720a0114acfb92a8ff718c02ba7f3170d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 24 Dec 2024 12:02:50 +0100
Subject: [PATCH 168/259] Implement operator_mathop

---
 src/dev/blocks/operatorblocks.cpp        |  43 ++++-
 src/dev/blocks/operatorblocks.h          |   1 +
 test/dev/blocks/operator_blocks_test.cpp | 202 +++++++++++++++++++++++
 3 files changed, 245 insertions(+), 1 deletion(-)

diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index 3cbd042a..b962d3c7 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -2,7 +2,9 @@
 
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/dev/compiler.h>
-#include <scratchcpp/dev/compilervalue.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/field.h>
+#include <scratchcpp/value.h>
 #include <utf8.h>
 
 #include "operatorblocks.h"
@@ -38,6 +40,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "operator_contains", &compileContains);
     engine->addCompileFunction(this, "operator_mod", &compileMod);
     engine->addCompileFunction(this, "operator_round", &compileRound);
+    engine->addCompileFunction(this, "operator_mathop", &compileMathOp);
 }
 
 CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler)
@@ -134,6 +137,44 @@ CompilerValue *OperatorBlocks::compileRound(Compiler *compiler)
     return compiler->createRound(compiler->addInput("NUM"));
 }
 
+CompilerValue *OperatorBlocks::compileMathOp(Compiler *compiler)
+{
+    Field *opField = compiler->field("OPERATOR");
+    const std::string numInput = "NUM";
+    const std::string &op = opField->value().toString();
+
+    if (op == "abs")
+        return compiler->createAbs(compiler->addInput(numInput));
+    else if (op == "floor")
+        return compiler->createFloor(compiler->addInput(numInput));
+    else if (op == "ceiling")
+        return compiler->createCeil(compiler->addInput(numInput));
+    else if (op == "sqrt")
+        return compiler->createSqrt(compiler->addInput(numInput));
+    else if (op == "sin")
+        return compiler->createSin(compiler->addInput(numInput));
+    else if (op == "cos")
+        return compiler->createCos(compiler->addInput(numInput));
+    else if (op == "tan")
+        return compiler->createTan(compiler->addInput(numInput));
+    else if (op == "asin")
+        return compiler->createAsin(compiler->addInput(numInput));
+    else if (op == "acos")
+        return compiler->createAcos(compiler->addInput(numInput));
+    else if (op == "atan")
+        return compiler->createAtan(compiler->addInput(numInput));
+    else if (op == "ln")
+        return compiler->createLn(compiler->addInput(numInput));
+    else if (op == "log")
+        return compiler->createLog10(compiler->addInput(numInput));
+    else if (op == "e ^")
+        return compiler->createExp(compiler->addInput(numInput));
+    else if (op == "10 ^")
+        return compiler->createExp10(compiler->addInput(numInput));
+    else
+        return compiler->addConstValue(Value());
+}
+
 extern "C" char *operator_join(const char *string1, const char *string2)
 {
     const size_t len1 = strlen(string1);
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index be39423c..1804e931 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -35,6 +35,7 @@ class OperatorBlocks : public IExtension
         static CompilerValue *compileContains(Compiler *compiler);
         static CompilerValue *compileMod(Compiler *compiler);
         static CompilerValue *compileRound(Compiler *compiler);
+        static CompilerValue *compileMathOp(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp
index 0947d46d..c67bde95 100644
--- a/test/dev/blocks/operator_blocks_test.cpp
+++ b/test/dev/blocks/operator_blocks_test.cpp
@@ -558,3 +558,205 @@ TEST_F(OperatorBlocksTest, Round)
     ASSERT_EQ(Value(values[0]), 6);
     ASSERT_EQ(Value(values[1]), 2);
 }
+
+TEST_F(OperatorBlocksTest, MathOp)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    // abs
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "abs");
+    builder.addValueInput("NUM", 5.7);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "abs");
+    builder.addValueInput("NUM", -5.7);
+    builder.captureBlockReturnValue();
+
+    // floor
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "floor");
+    builder.addValueInput("NUM", 3.2);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "floor");
+    builder.addValueInput("NUM", 5.7);
+    builder.captureBlockReturnValue();
+
+    // ceiling
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "ceiling");
+    builder.addValueInput("NUM", 3.2);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "ceiling");
+    builder.addValueInput("NUM", 5.7);
+    builder.captureBlockReturnValue();
+
+    // sqrt
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "sqrt");
+    builder.addValueInput("NUM", 16);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "sqrt");
+    builder.addValueInput("NUM", 2);
+    builder.captureBlockReturnValue();
+
+    // sin
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "sin");
+    builder.addValueInput("NUM", 90);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "sin");
+    builder.addValueInput("NUM", 30);
+    builder.captureBlockReturnValue();
+
+    // cos
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "cos");
+    builder.addValueInput("NUM", 0);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "cos");
+    builder.addValueInput("NUM", 60);
+    builder.captureBlockReturnValue();
+
+    // tan
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "tan");
+    builder.addValueInput("NUM", 30);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "tan");
+    builder.addValueInput("NUM", 45);
+    builder.captureBlockReturnValue();
+
+    // asin
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "asin");
+    builder.addValueInput("NUM", 1);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "asin");
+    builder.addValueInput("NUM", 0.5);
+    builder.captureBlockReturnValue();
+
+    // acos
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "acos");
+    builder.addValueInput("NUM", 1);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "acos");
+    builder.addValueInput("NUM", 0.5);
+    builder.captureBlockReturnValue();
+
+    // atan
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "atan");
+    builder.addValueInput("NUM", 0.5);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "atan");
+    builder.addValueInput("NUM", 1);
+    builder.captureBlockReturnValue();
+
+    // ln
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "ln");
+    builder.addValueInput("NUM", 1);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "ln");
+    builder.addValueInput("NUM", 10);
+    builder.captureBlockReturnValue();
+
+    // log
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "log");
+    builder.addValueInput("NUM", 1);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "log");
+    builder.addValueInput("NUM", 100);
+    builder.captureBlockReturnValue();
+
+    // e ^
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "e ^");
+    builder.addValueInput("NUM", 1);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "e ^");
+    builder.addValueInput("NUM", 8.2);
+    builder.captureBlockReturnValue();
+
+    // 10 ^
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "10 ^");
+    builder.addValueInput("NUM", 1);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "10 ^");
+    builder.addValueInput("NUM", 8.2);
+    builder.captureBlockReturnValue();
+
+    // invalid
+    builder.addBlock("operator_mathop");
+    builder.addDropdownField("OPERATOR", "invalid");
+    builder.addValueInput("NUM", -5.54);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 29);
+    ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 5.7);
+    ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 5.7);
+    ASSERT_EQ(std::round(value_toDouble(&values[2]) * 100) / 100, 3);
+    ASSERT_EQ(std::round(value_toDouble(&values[3]) * 100) / 100, 5);
+    ASSERT_EQ(std::round(value_toDouble(&values[4]) * 100) / 100, 4);
+    ASSERT_EQ(std::round(value_toDouble(&values[5]) * 100) / 100, 6);
+    ASSERT_EQ(std::round(value_toDouble(&values[6]) * 100) / 100, 4);
+    ASSERT_EQ(std::round(value_toDouble(&values[7]) * 100) / 100, 1.41);
+    ASSERT_EQ(std::round(value_toDouble(&values[8]) * 100) / 100, 1);
+    ASSERT_EQ(std::round(value_toDouble(&values[9]) * 100) / 100, 0.5);
+    ASSERT_EQ(std::round(value_toDouble(&values[10]) * 100) / 100, 1);
+    ASSERT_EQ(std::round(value_toDouble(&values[11]) * 100) / 100, 0.5);
+    ASSERT_EQ(std::round(value_toDouble(&values[12]) * 100) / 100, 0.58);
+    ASSERT_EQ(std::round(value_toDouble(&values[13]) * 100) / 100, 1);
+    ASSERT_EQ(std::round(value_toDouble(&values[14]) * 100) / 100, 90);
+    ASSERT_EQ(std::round(value_toDouble(&values[15]) * 100) / 100, 30);
+    ASSERT_EQ(std::round(value_toDouble(&values[16]) * 100) / 100, 0);
+    ASSERT_EQ(std::round(value_toDouble(&values[17]) * 100) / 100, 60);
+    ASSERT_EQ(std::round(value_toDouble(&values[18]) * 100) / 100, 26.57);
+    ASSERT_EQ(std::round(value_toDouble(&values[19]) * 100) / 100, 45);
+    ASSERT_EQ(std::round(value_toDouble(&values[20]) * 100) / 100, 0);
+    ASSERT_EQ(std::round(value_toDouble(&values[21]) * 100) / 100, 2.3);
+    ASSERT_EQ(std::round(value_toDouble(&values[22]) * 100) / 100, 0);
+    ASSERT_EQ(std::round(value_toDouble(&values[23]) * 100) / 100, 2);
+    ASSERT_EQ(std::round(value_toDouble(&values[24]) * 100) / 100, 2.72);
+    ASSERT_EQ(std::round(value_toDouble(&values[25]) * 100) / 100, 3640.95);
+    ASSERT_EQ(std::round(value_toDouble(&values[26]) * 100) / 100, 10);
+    ASSERT_EQ(std::round(value_toDouble(&values[27]) * 100) / 100, 158489319.25);
+    ASSERT_EQ(std::round(value_toDouble(&values[28]) * 100) / 100, 0);
+}

From 7e8502a290de25a4ba97c9b2415141c302a5e1e1 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 25 Dec 2024 11:35:26 +0100
Subject: [PATCH 169/259] RandomGenerator: Add missing 'override'

---
 src/engine/internal/randomgenerator.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/engine/internal/randomgenerator.h b/src/engine/internal/randomgenerator.h
index 69d7cabd..1db28b69 100644
--- a/src/engine/internal/randomgenerator.h
+++ b/src/engine/internal/randomgenerator.h
@@ -19,7 +19,7 @@ class RandomGenerator : public IRandomGenerator
 
         long randint(long start, long end) const override;
         double randintDouble(double start, double end) const override;
-        long randintExcept(long start, long end, long except) const;
+        long randintExcept(long start, long end, long except) const override;
 
     private:
         static std::shared_ptr<RandomGenerator> m_instance;

From 7ed99b5a1d0a88c3ebda90410546650cd6d6f690 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 25 Dec 2024 12:38:18 +0100
Subject: [PATCH 170/259] Implement data_variable

---
 src/dev/blocks/variableblocks.cpp        | 20 +++++++++++
 src/dev/blocks/variableblocks.h          |  3 ++
 test/dev/blocks/variable_blocks_test.cpp | 43 +++++++++++++++++++++++-
 3 files changed, 65 insertions(+), 1 deletion(-)

diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp
index 6d0f464f..545a68ea 100644
--- a/src/dev/blocks/variableblocks.cpp
+++ b/src/dev/blocks/variableblocks.cpp
@@ -1,5 +1,12 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
+#include <scratchcpp/variable.h>
+
 #include "variableblocks.h"
 
 using namespace libscratchcpp;
@@ -16,4 +23,17 @@ std::string VariableBlocks::description() const
 
 void VariableBlocks::registerBlocks(IEngine *engine)
 {
+    engine->addCompileFunction(this, "data_variable", &compileVariable);
+}
+
+CompilerValue *VariableBlocks::compileVariable(Compiler *compiler)
+{
+    Field *varField = compiler->field("VARIABLE");
+    Variable *var = static_cast<Variable *>(varField->valuePtr().get());
+    assert(var);
+
+    if (var)
+        return compiler->addVariableValue(var);
+    else
+        return compiler->addConstValue(Value());
 }
diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h
index 6c4144ac..3f579880 100644
--- a/src/dev/blocks/variableblocks.h
+++ b/src/dev/blocks/variableblocks.h
@@ -14,6 +14,9 @@ class VariableBlocks : public IExtension
         std::string description() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+    private:
+        static CompilerValue *compileVariable(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/variable_blocks_test.cpp b/test/dev/blocks/variable_blocks_test.cpp
index 0a480f33..a21343bd 100644
--- a/test/dev/blocks/variable_blocks_test.cpp
+++ b/test/dev/blocks/variable_blocks_test.cpp
@@ -1,15 +1,56 @@
+#include <scratchcpp/project.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/test/scriptbuilder.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "dev/blocks/variableblocks.h"
 
 using namespace libscratchcpp;
+using namespace libscratchcpp::test;
 
 class VariableBlocksTest : public testing::Test
 {
     public:
-        void SetUp() override { m_extension = std::make_unique<VariableBlocks>(); }
+        void SetUp() override
+        {
+            m_extension = std::make_unique<VariableBlocks>();
+            m_engine = m_project.engine().get();
+            m_extension->registerBlocks(m_engine);
+        }
 
         std::unique_ptr<IExtension> m_extension;
+        Project m_project;
+        IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
 };
+
+TEST_F(VariableBlocksTest, Variable)
+{
+    auto target = std::make_shared<Sprite>();
+    auto var1 = std::make_shared<Variable>("", "", 835.21);
+    target->addVariable(var1);
+    auto var2 = std::make_shared<Variable>("", "", "Hello world");
+    target->addVariable(var2);
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("data_variable");
+    builder.addEntityField("VARIABLE", var1);
+    builder.captureBlockReturnValue();
+
+    builder.addBlock("data_variable");
+    builder.addEntityField("VARIABLE", var2);
+    builder.captureBlockReturnValue();
+
+    builder.build();
+    builder.run();
+
+    List *valueList = builder.capturedValues();
+    ValueData *values = valueList->data();
+    ASSERT_EQ(valueList->size(), 2);
+    ASSERT_EQ(Value(values[0]), 835.21);
+    ASSERT_EQ(Value(values[1]), "Hello world");
+}

From 5d2b4253c45bb5a4aba7a5715477a453cbe8a0f9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 25 Dec 2024 12:38:59 +0100
Subject: [PATCH 171/259] Implement data_setvariableto

---
 src/dev/blocks/variableblocks.cpp        | 13 +++++++++++++
 src/dev/blocks/variableblocks.h          |  1 +
 test/dev/blocks/variable_blocks_test.cpp | 23 +++++++++++++++++++++++
 3 files changed, 37 insertions(+)

diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp
index 545a68ea..7701502c 100644
--- a/src/dev/blocks/variableblocks.cpp
+++ b/src/dev/blocks/variableblocks.cpp
@@ -24,6 +24,7 @@ std::string VariableBlocks::description() const
 void VariableBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "data_variable", &compileVariable);
+    engine->addCompileFunction(this, "data_setvariableto", &compileSetVariableTo);
 }
 
 CompilerValue *VariableBlocks::compileVariable(Compiler *compiler)
@@ -37,3 +38,15 @@ CompilerValue *VariableBlocks::compileVariable(Compiler *compiler)
     else
         return compiler->addConstValue(Value());
 }
+
+CompilerValue *VariableBlocks::compileSetVariableTo(Compiler *compiler)
+{
+    Field *varField = compiler->field("VARIABLE");
+    Variable *var = static_cast<Variable *>(varField->valuePtr().get());
+    assert(var);
+
+    if (var)
+        compiler->createVariableWrite(var, compiler->addInput("VALUE"));
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h
index 3f579880..cb563fca 100644
--- a/src/dev/blocks/variableblocks.h
+++ b/src/dev/blocks/variableblocks.h
@@ -17,6 +17,7 @@ class VariableBlocks : public IExtension
 
     private:
         static CompilerValue *compileVariable(Compiler *compiler);
+        static CompilerValue *compileSetVariableTo(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/variable_blocks_test.cpp b/test/dev/blocks/variable_blocks_test.cpp
index a21343bd..c7cc5694 100644
--- a/test/dev/blocks/variable_blocks_test.cpp
+++ b/test/dev/blocks/variable_blocks_test.cpp
@@ -54,3 +54,26 @@ TEST_F(VariableBlocksTest, Variable)
     ASSERT_EQ(Value(values[0]), 835.21);
     ASSERT_EQ(Value(values[1]), "Hello world");
 }
+
+TEST_F(VariableBlocksTest, SetVariableTo)
+{
+    auto target = std::make_shared<Sprite>();
+    auto var1 = std::make_shared<Variable>("", "", 835.21);
+    target->addVariable(var1);
+    auto var2 = std::make_shared<Variable>("", "", "Hello world");
+    target->addVariable(var2);
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("data_setvariableto");
+    builder.addEntityField("VARIABLE", var1);
+    builder.addValueInput("VALUE", "test");
+
+    builder.addBlock("data_setvariableto");
+    builder.addEntityField("VARIABLE", var2);
+    builder.addValueInput("VALUE", 123);
+
+    builder.build();
+    builder.run();
+    ASSERT_EQ(var1->value(), "test");
+    ASSERT_EQ(var2->value(), 123);
+}

From f09dce90d755125d4c8c615f8216f2a60fdb1fe8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 25 Dec 2024 12:52:15 +0100
Subject: [PATCH 172/259] Implement data_changevariableby

---
 src/dev/blocks/variableblocks.cpp        | 15 +++++++++++
 src/dev/blocks/variableblocks.h          |  1 +
 test/dev/blocks/variable_blocks_test.cpp | 32 ++++++++++++++++++++++++
 3 files changed, 48 insertions(+)

diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp
index 7701502c..177d8dab 100644
--- a/src/dev/blocks/variableblocks.cpp
+++ b/src/dev/blocks/variableblocks.cpp
@@ -25,6 +25,7 @@ void VariableBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "data_variable", &compileVariable);
     engine->addCompileFunction(this, "data_setvariableto", &compileSetVariableTo);
+    engine->addCompileFunction(this, "data_changevariableby", &compileChangeVariableBy);
 }
 
 CompilerValue *VariableBlocks::compileVariable(Compiler *compiler)
@@ -50,3 +51,17 @@ CompilerValue *VariableBlocks::compileSetVariableTo(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *VariableBlocks::compileChangeVariableBy(Compiler *compiler)
+{
+    Field *varField = compiler->field("VARIABLE");
+    Variable *var = static_cast<Variable *>(varField->valuePtr().get());
+    assert(var);
+
+    if (var) {
+        CompilerValue *value = compiler->createAdd(compiler->addVariableValue(var), compiler->addInput("VALUE"));
+        compiler->createVariableWrite(var, value);
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h
index cb563fca..9cb0bcc4 100644
--- a/src/dev/blocks/variableblocks.h
+++ b/src/dev/blocks/variableblocks.h
@@ -18,6 +18,7 @@ class VariableBlocks : public IExtension
     private:
         static CompilerValue *compileVariable(Compiler *compiler);
         static CompilerValue *compileSetVariableTo(Compiler *compiler);
+        static CompilerValue *compileChangeVariableBy(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/variable_blocks_test.cpp b/test/dev/blocks/variable_blocks_test.cpp
index c7cc5694..2789f289 100644
--- a/test/dev/blocks/variable_blocks_test.cpp
+++ b/test/dev/blocks/variable_blocks_test.cpp
@@ -77,3 +77,35 @@ TEST_F(VariableBlocksTest, SetVariableTo)
     ASSERT_EQ(var1->value(), "test");
     ASSERT_EQ(var2->value(), 123);
 }
+
+TEST_F(VariableBlocksTest, ChangeVariableBy)
+{
+    auto target = std::make_shared<Sprite>();
+    auto var1 = std::make_shared<Variable>("", "", 835.21);
+    target->addVariable(var1);
+    auto var2 = std::make_shared<Variable>("", "", "Hello world");
+    target->addVariable(var2);
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("data_changevariableby");
+    builder.addEntityField("VARIABLE", var1);
+    builder.addValueInput("VALUE", "5.12");
+
+    builder.addBlock("data_changevariableby");
+    builder.addEntityField("VARIABLE", var2);
+    builder.addValueInput("VALUE", -2.5);
+
+    builder.build();
+
+    builder.run();
+    ASSERT_EQ(var1->value(), 840.33);
+    ASSERT_EQ(var2->value(), -2.5);
+
+    builder.run();
+    ASSERT_EQ(var1->value(), 845.45);
+    ASSERT_EQ(var2->value(), -5);
+
+    builder.run();
+    ASSERT_EQ(var1->value(), 850.57);
+    ASSERT_EQ(var2->value(), -7.5);
+}

From e4944e1307196114327a8b29a4284abf2969be25 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 25 Dec 2024 23:14:27 +0100
Subject: [PATCH 173/259] Temporarily disable broadcast stops wait blocks
 regtest (LLVM)

---
 test/engine/engine_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index a648739f..8944cac0 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -2253,6 +2253,7 @@ TEST(EngineTest, DuplicateVariableOrListIDs)
     ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool());
 }
 
+#ifndef USE_LLVM
 TEST(EngineTest, BroadcastStopsWaitBlocks)
 {
     // Regtest for #563
@@ -2272,7 +2273,6 @@ TEST(EngineTest, BroadcastStopsWaitBlocks)
     ASSERT_TRUE(GET_VAR(stage, "backdrop_passed")->value().toBool());
 }
 
-#ifndef USE_LLVM
 TEST(EngineTest, BroadcastAndWaitCaseInsensitive)
 {
     // Regtest for #578

From d1a82e466e227d8a484f234eaa80e36cfdceba29 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 26 Dec 2024 15:24:57 +0100
Subject: [PATCH 174/259] ScriptBuilder: Fix multiple entity references

---
 src/dev/test/scriptbuilder.cpp           | 27 +++++++++++++++++---
 src/dev/test/scriptbuilder_p.h           |  2 ++
 test/dev/test_api/scriptbuilder_test.cpp | 32 +++++++++++++++---------
 3 files changed, 45 insertions(+), 16 deletions(-)

diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index ea232b07..51ec57e7 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -174,7 +174,11 @@ void ScriptBuilder::addEntityInput(const std::string &name, const std::string &e
     if (!impl->lastBlock)
         return;
 
-    entity->setId(std::to_string(impl->blockId++));
+    if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
+        entity->setId(std::to_string(impl->blockId++));
+        impl->entities.push_back(entity);
+    }
+
     auto input = std::make_shared<Input>(name, Input::Type::Shadow);
     input->setPrimaryValue(entityName);
     input->primaryValue()->setValuePtr(entity);
@@ -188,7 +192,11 @@ void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptr<Enti
     if (!impl->lastBlock)
         return;
 
-    entity->setId(std::to_string(impl->blockId++));
+    if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
+        entity->setId(std::to_string(impl->blockId++));
+        impl->entities.push_back(entity);
+    }
+
     auto field = std::make_shared<Field>(name, Value(), entity);
     impl->lastBlock->addField(field);
 }
@@ -203,8 +211,19 @@ std::shared_ptr<Block> ScriptBuilder::currentBlock()
     if (!impl->lastBlock)
         return nullptr;
 
-    if (!impl->lastBlock->compileFunction())
-        build(std::make_shared<Sprite>());
+    if (!impl->lastBlock->compileFunction()) {
+        auto target = std::make_shared<Sprite>();
+        const auto &variables = impl->target->variables();
+        const auto &lists = impl->target->lists();
+
+        for (auto var : variables)
+            target->addVariable(var);
+
+        for (auto list : lists)
+            target->addList(list);
+
+        build(target);
+    }
 
     return impl->lastBlock;
 }
diff --git a/src/dev/test/scriptbuilder_p.h b/src/dev/test/scriptbuilder_p.h
index 9b332b54..67688bd5 100644
--- a/src/dev/test/scriptbuilder_p.h
+++ b/src/dev/test/scriptbuilder_p.h
@@ -11,6 +11,7 @@ namespace libscratchcpp
 class IEngine;
 class Target;
 class Block;
+class Entity;
 class List;
 
 } // namespace libscratchcpp
@@ -29,6 +30,7 @@ class ScriptBuilderPrivate
         std::shared_ptr<Block> lastBlock;
         std::vector<std::shared_ptr<Block>> blocks;
         std::vector<std::shared_ptr<Block>> inputBlocks;
+        std::vector<std::shared_ptr<Entity>> entities;
         unsigned int blockId = 0;
 };
 
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index 3191d63f..058caaf6 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -7,7 +7,7 @@
 #include <scratchcpp/input.h>
 #include <scratchcpp/field.h>
 #include <scratchcpp/list.h>
-#include <scratchcpp/broadcast.h>
+#include <scratchcpp/variable.h>
 
 #include "../../common.h"
 #include "testextension.h"
@@ -183,34 +183,42 @@ TEST_F(ScriptBuilderTest, AddDropdownField)
 
 TEST_F(ScriptBuilderTest, AddEntityInput)
 {
-    auto broadcast = std::make_shared<Broadcast>("", "");
-    m_engine->setBroadcasts({ broadcast });
+    auto var = std::make_shared<Variable>("", "");
+    m_target->addVariable(var);
 
     m_builder->addBlock("test_simple");
-    m_builder->addEntityInput("BROADCAST", "test", InputValue::Type::Broadcast, broadcast);
+    m_builder->addEntityInput("VARIABLE", "test", InputValue::Type::Variable, var);
     auto block = m_builder->currentBlock();
     ASSERT_TRUE(block);
     ASSERT_EQ(block->opcode(), "test_simple");
     ASSERT_EQ(block->inputs().size(), 1);
-    ASSERT_EQ(block->inputAt(0)->name(), "BROADCAST");
-    ASSERT_EQ(block->inputAt(0)->primaryValue()->valuePtr(), broadcast);
-    ASSERT_EQ(block->inputAt(0)->primaryValue()->type(), InputValue::Type::Broadcast);
+    ASSERT_EQ(block->inputAt(0)->name(), "VARIABLE");
+    ASSERT_EQ(block->inputAt(0)->primaryValue()->valuePtr(), var);
+    ASSERT_EQ(block->inputAt(0)->primaryValue()->type(), InputValue::Type::Variable);
+
+    m_builder->addBlock("test_simple");
+    m_builder->addEntityInput("VARIABLE", "test", InputValue::Type::Variable, var);
+    m_builder->build();
 }
 
 TEST_F(ScriptBuilderTest, AddEntityField)
 {
-    auto broadcast = std::make_shared<Broadcast>("", "");
-    m_engine->setBroadcasts({ broadcast });
+    auto var = std::make_shared<Variable>("", "");
+    m_target->addVariable(var);
 
     m_builder->addBlock("test_simple");
-    m_builder->addEntityField("BROADCAST", broadcast);
+    m_builder->addEntityField("VARIABLE", var);
     auto block = m_builder->currentBlock();
     ASSERT_TRUE(block);
     ASSERT_EQ(block->opcode(), "test_simple");
     ASSERT_TRUE(block->inputs().empty());
     ASSERT_EQ(block->fields().size(), 1);
-    ASSERT_EQ(block->fieldAt(0)->name(), "BROADCAST");
-    ASSERT_EQ(block->fieldAt(0)->valuePtr(), broadcast);
+    ASSERT_EQ(block->fieldAt(0)->name(), "VARIABLE");
+    ASSERT_EQ(block->fieldAt(0)->valuePtr(), var);
+
+    m_builder->addBlock("test_simple");
+    m_builder->addEntityField("VARIABLE", var);
+    m_builder->build();
 }
 
 TEST_F(ScriptBuilderTest, ReporterBlocks)

From 078c4ab00c56bb191b4f126f3bbd7141bef27b0e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 26 Dec 2024 15:25:29 +0100
Subject: [PATCH 175/259] Implement data_addtolist

---
 src/dev/blocks/listblocks.cpp        | 18 +++++++++++
 src/dev/blocks/listblocks.h          |  3 ++
 test/dev/blocks/list_blocks_test.cpp | 48 +++++++++++++++++++++++++++-
 3 files changed, 68 insertions(+), 1 deletion(-)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index 8b4602ad..a6999704 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -1,5 +1,11 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/field.h>
+#include <scratchcpp/list.h>
+
 #include "listblocks.h"
 
 using namespace libscratchcpp;
@@ -16,4 +22,16 @@ std::string ListBlocks::description() const
 
 void ListBlocks::registerBlocks(IEngine *engine)
 {
+    engine->addCompileFunction(this, "data_addtolist", &compileAddToList);
+}
+
+CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
+{
+    auto list = compiler->field("LIST")->valuePtr();
+    assert(list);
+
+    if (list)
+        compiler->createListAppend(static_cast<List *>(list.get()), compiler->addInput("ITEM"));
+
+    return nullptr;
 }
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index edf01e8c..9163b635 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -14,6 +14,9 @@ class ListBlocks : public IExtension
         std::string description() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+    private:
+        static CompilerValue *compileAddToList(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index 8f98a2fb..b18c169a 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -1,15 +1,61 @@
+#include <scratchcpp/project.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/list.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/test/scriptbuilder.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "dev/blocks/listblocks.h"
 
 using namespace libscratchcpp;
+using namespace libscratchcpp::test;
 
 class ListBlocksTest : public testing::Test
 {
     public:
-        void SetUp() override { m_extension = std::make_unique<ListBlocks>(); }
+        void SetUp() override
+        {
+            m_extension = std::make_unique<ListBlocks>();
+            m_engine = m_project.engine().get();
+            m_extension->registerBlocks(m_engine);
+        }
 
         std::unique_ptr<IExtension> m_extension;
+        Project m_project;
+        IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
 };
+
+TEST_F(ListBlocksTest, AddToList)
+{
+    auto target = std::make_shared<Sprite>();
+    auto list1 = std::make_shared<List>("", "");
+    target->addList(list1);
+    auto list2 = std::make_shared<List>("", "");
+    target->addList(list2);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("data_addtolist");
+    builder.addValueInput("ITEM", "test");
+    builder.addEntityField("LIST", list1);
+
+    builder.addBlock("data_addtolist");
+    builder.addValueInput("ITEM", true);
+    builder.addEntityField("LIST", list1);
+
+    builder.addBlock("data_addtolist");
+    builder.addValueInput("ITEM", 123);
+    builder.addEntityField("LIST", list2);
+
+    builder.addBlock("data_addtolist");
+    builder.addValueInput("ITEM", "Hello world");
+    builder.addEntityField("LIST", list2);
+
+    builder.build();
+
+    builder.run();
+    ASSERT_EQ(list1->toString(), "test true");
+    ASSERT_EQ(list2->toString(), "123 Hello world");
+}

From af642a4c1cf8cf670ceccd31781b0907bd86aa61 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 13:29:03 +0100
Subject: [PATCH 176/259] Fix Infinity and NaN to int cast

---
 src/scratch/value_functions.cpp     |  4 ++--
 test/scratch_classes/value_test.cpp | 14 ++++++++++++++
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp
index 0da37ff3..d0c85537 100644
--- a/src/scratch/value_functions.cpp
+++ b/src/scratch/value_functions.cpp
@@ -192,7 +192,7 @@ extern "C"
     long value_toLong(const libscratchcpp::ValueData *v)
     {
         if (v->type == ValueType::Number) {
-            return v->numberValue;
+            return std::isnan(v->numberValue) || std::isinf(v->numberValue) ? 0 : v->numberValue;
         } else if (v->type == ValueType::Bool)
             return v->boolValue;
         else if (v->type == ValueType::String)
@@ -205,7 +205,7 @@ extern "C"
     int value_toInt(const libscratchcpp::ValueData *v)
     {
         if (v->type == ValueType::Number)
-            return v->numberValue;
+            return std::isnan(v->numberValue) || std::isinf(v->numberValue) ? 0 : v->numberValue;
         else if (v->type == ValueType::Bool)
             return v->boolValue;
         else if (v->type == ValueType::String)
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index f59f3078..901d44ad 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -653,6 +653,13 @@ TEST(ValueTest, ToInt)
     v = "NaN";
     ASSERT_EQ(v.toInt(), 0);
 
+    v = std::numeric_limits<double>::infinity();
+    ASSERT_EQ(v.toInt(), 0);
+    v = -std::numeric_limits<double>::infinity();
+    ASSERT_EQ(v.toInt(), 0);
+    v = std::numeric_limits<double>::quiet_NaN();
+    ASSERT_EQ(v.toInt(), 0);
+
     v = "something";
     ASSERT_EQ(v.toInt(), 0);
 
@@ -795,6 +802,13 @@ TEST(ValueTest, ToLong)
     v = "NaN";
     ASSERT_EQ(v.toLong(), 0);
 
+    v = std::numeric_limits<double>::infinity();
+    ASSERT_EQ(v.toLong(), 0);
+    v = -std::numeric_limits<double>::infinity();
+    ASSERT_EQ(v.toLong(), 0);
+    v = std::numeric_limits<double>::quiet_NaN();
+    ASSERT_EQ(v.toLong(), 0);
+
     v = "something";
     ASSERT_EQ(v.toLong(), 0);
 

From 6a9b2f6b7d232ec01410cbbec8e7927d860ddde6 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 13:29:39 +0100
Subject: [PATCH 177/259] LLVMCodeBuilder: Add random test with NaN

---
 test/dev/llvm/llvmcodebuilder_test.cpp | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index fa631be4..31f02bfc 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -698,6 +698,16 @@ TEST_F(LLVMCodeBuilderTest, Random)
 
     const double inf = std::numeric_limits<double>::infinity();
     const double nan = std::numeric_limits<double>::quiet_NaN();
+
+    EXPECT_CALL(m_rng, randint(0, 5)).Times(3).WillRepeatedly(Return(5));
+    runOpTest(OpType::Random, nan, 5);
+
+    EXPECT_CALL(m_rng, randint(5, 0)).Times(3).WillRepeatedly(Return(3));
+    runOpTest(OpType::Random, 5, nan);
+
+    EXPECT_CALL(m_rng, randint(0, 0)).Times(3).WillRepeatedly(Return(0));
+    runOpTest(OpType::Random, nan, nan);
+
     EXPECT_CALL(m_rng, randint).WillRepeatedly(Return(0));
     EXPECT_CALL(m_rng, randintDouble).WillRepeatedly(Return(0));
 

From f6396e9b4c718c76241056723ef24e1645ca7f52 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 13:46:47 +0100
Subject: [PATCH 178/259] LLVMCodeBuilder: Add createRandomInt() method

---
 src/dev/engine/internal/icodebuilder.h        |  1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 21 ++++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +
 .../engine/internal/llvm/llvmfunctions.cpp    |  5 +++
 .../engine/internal/llvm/llvminstruction.h    |  1 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 42 +++++++++++++++++++
 test/mocks/codebuildermock.h                  |  1 +
 7 files changed, 73 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index d65a24f0..d42db981 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -37,6 +37,7 @@ class ICodeBuilder
         virtual CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) = 0;
 
         virtual CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) = 0;
+        virtual CompilerValue *createRandomInt(CompilerValue *from, CompilerValue *to) = 0;
 
         virtual CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 74698bf2..546d5b48 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -223,6 +223,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::RandomInt: {
+                assert(step.args.size() == 2);
+                const auto &arg1 = step.args[0];
+                const auto &arg2 = step.args[1];
+                llvm::Value *from = m_builder.CreateFPToSI(castValue(arg1.second, arg1.first), m_builder.getInt64Ty());
+                llvm::Value *to = m_builder.CreateFPToSI(castValue(arg2.second, arg2.first), m_builder.getInt64Ty());
+                step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_long(), { executionContextPtr, from, to });
+                break;
+            }
+
             case LLVMInstruction::Type::CmpEQ: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0].second;
@@ -1126,6 +1136,11 @@ CompilerValue *LLVMCodeBuilder::createRandom(CompilerValue *from, CompilerValue
     return createOp(LLVMInstruction::Type::Random, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { from, to });
 }
 
+CompilerValue *LLVMCodeBuilder::createRandomInt(CompilerValue *from, CompilerValue *to)
+{
+    return createOp(LLVMInstruction::Type::RandomInt, Compiler::StaticType::Number, Compiler::StaticType::Number, { from, to });
+}
+
 CompilerValue *LLVMCodeBuilder::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2)
 {
     return createOp(LLVMInstruction::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 });
@@ -2384,6 +2399,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double()
     return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_long()
+{
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    return resolveFunction("llvm_random_long", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt64Ty(), m_builder.getInt64Ty() }, false));
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 62e4aadf..bd18635f 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -44,6 +44,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) override;
 
         CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) override;
+        CompilerValue *createRandomInt(CompilerValue *from, CompilerValue *to) override;
 
         CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) override;
@@ -170,6 +171,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_list_to_string();
         llvm::FunctionCallee resolve_llvm_random();
         llvm::FunctionCallee resolve_llvm_random_double();
+        llvm::FunctionCallee resolve_llvm_random_long();
         llvm::FunctionCallee resolve_llvm_random_bool();
         llvm::FunctionCallee resolve_strcasecmp();
 
diff --git a/src/dev/engine/internal/llvm/llvmfunctions.cpp b/src/dev/engine/internal/llvm/llvmfunctions.cpp
index d07be391..eafd9a8c 100644
--- a/src/dev/engine/internal/llvm/llvmfunctions.cpp
+++ b/src/dev/engine/internal/llvm/llvmfunctions.cpp
@@ -19,6 +19,11 @@ extern "C"
         return value_doubleIsInt(from) && value_doubleIsInt(to) ? ctx->rng()->randint(from, to) : ctx->rng()->randintDouble(from, to);
     }
 
+    double llvm_random_long(ExecutionContext *ctx, long from, long to)
+    {
+        return ctx->rng()->randint(from, to);
+    }
+
     double llvm_random_bool(ExecutionContext *ctx, bool from, bool to)
     {
         return ctx->rng()->randint(from, to);
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index f3d2fa7b..35034cca 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -19,6 +19,7 @@ struct LLVMInstruction
             Mul,
             Div,
             Random,
+            RandomInt,
             CmpEQ,
             CmpGT,
             CmpLT,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 31f02bfc..fae22ef0 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -30,6 +30,7 @@ class LLVMCodeBuilderTest : public testing::Test
             Mul,
             Div,
             Random,
+            RandomInt,
             CmpEQ,
             CmpGT,
             CmpLT,
@@ -98,6 +99,9 @@ class LLVMCodeBuilderTest : public testing::Test
                 case OpType::Random:
                     return m_builder->createRandom(arg1, arg2);
 
+                case OpType::RandomInt:
+                    return m_builder->createRandomInt(arg1, arg2);
+
                 case OpType::CmpEQ:
                     return m_builder->createCmpEQ(arg1, arg2);
 
@@ -203,6 +207,9 @@ class LLVMCodeBuilderTest : public testing::Test
                     return v1.isInt() && v2.isInt() ? m_rng.randint(v1.toLong(), v2.toLong()) : m_rng.randintDouble(v1.toDouble(), v2.toDouble());
                 }
 
+                case OpType::RandomInt:
+                    return m_rng.randint(v1.toLong(), v2.toLong());
+
                 case OpType::CmpEQ:
                     return v1 == v2;
 
@@ -727,6 +734,41 @@ TEST_F(LLVMCodeBuilderTest, Random)
     runOpTest(OpType::Random, -inf, inf, nan);
 }
 
+TEST_F(LLVMCodeBuilderTest, RandomInt)
+{
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18));
+    runOpTest(OpType::RandomInt, -45, 12);
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5));
+    runOpTest(OpType::RandomInt, -45.0, 12.0);
+
+    EXPECT_CALL(m_rng, randint(12, 6)).Times(3).WillRepeatedly(Return(3));
+    runOpTest(OpType::RandomInt, 12, 6.05);
+
+    EXPECT_CALL(m_rng, randint(-78, -45)).Times(3).WillRepeatedly(Return(-59));
+    runOpTest(OpType::RandomInt, -78.686, -45);
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0));
+    runOpTest(OpType::RandomInt, "-45", "12");
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5));
+    runOpTest(OpType::RandomInt, "-45.0", "12");
+
+    EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-15));
+    runOpTest(OpType::RandomInt, "-45", "12.0");
+
+    EXPECT_CALL(m_rng, randint(0, 1)).Times(3).WillRepeatedly(Return(1));
+    runOpTest(OpType::RandomInt, false, true);
+
+    EXPECT_CALL(m_rng, randint(1, 5)).Times(3).WillRepeatedly(Return(1));
+    runOpTest(OpType::RandomInt, true, 5);
+
+    EXPECT_CALL(m_rng, randint(8, 0)).Times(3).WillRepeatedly(Return(1));
+    runOpTest(OpType::RandomInt, 8, false);
+
+    // NOTE: Infinity, -Infinity and NaN behavior is undefined
+}
+
 TEST_F(LLVMCodeBuilderTest, EqualComparison)
 {
     runOpTest(OpType::CmpEQ, 10, 10);
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index bd381144..674abc8d 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -27,6 +27,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(CompilerValue *, createDiv, (CompilerValue *, CompilerValue *), (override));
 
         MOCK_METHOD(CompilerValue *, createRandom, (CompilerValue *, CompilerValue *), (override));
+        MOCK_METHOD(CompilerValue *, createRandomInt, (CompilerValue *, CompilerValue *), (override));
 
         MOCK_METHOD(CompilerValue *, createCmpEQ, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createCmpGT, (CompilerValue *, CompilerValue *), (override));

From ab250914bd8348fade751cd8e966bc1846ce09ff Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 15:52:07 +0100
Subject: [PATCH 179/259] Add CompilerLocalVariable class

---
 CMakeLists.txt                                |  1 +
 .../scratchcpp/dev/compilerlocalvariable.h    | 26 +++++++++++++++
 src/dev/engine/CMakeLists.txt                 |  3 ++
 src/dev/engine/compilerlocalvariable.cpp      | 23 +++++++++++++
 src/dev/engine/compilerlocalvariable_p.cpp    | 13 ++++++++
 src/dev/engine/compilerlocalvariable_p.h      | 18 ++++++++++
 test/dev/compiler/CMakeLists.txt              |  1 +
 .../compiler/compilerlocalvariable_test.cpp   | 33 +++++++++++++++++++
 8 files changed, 118 insertions(+)
 create mode 100644 include/scratchcpp/dev/compilerlocalvariable.h
 create mode 100644 src/dev/engine/compilerlocalvariable.cpp
 create mode 100644 src/dev/engine/compilerlocalvariable_p.cpp
 create mode 100644 src/dev/engine/compilerlocalvariable_p.h
 create mode 100644 test/dev/compiler/compilerlocalvariable_test.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1aaebf77..95f74700 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -76,6 +76,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
         include/scratchcpp/dev/compiler.h
         include/scratchcpp/dev/compilervalue.h
         include/scratchcpp/dev/compilerconstant.h
+        include/scratchcpp/dev/compilerlocalvariable.h
         include/scratchcpp/dev/executablecode.h
         include/scratchcpp/dev/executioncontext.h
         include/scratchcpp/dev/promise.h
diff --git a/include/scratchcpp/dev/compilerlocalvariable.h b/include/scratchcpp/dev/compilerlocalvariable.h
new file mode 100644
index 00000000..455a3ae4
--- /dev/null
+++ b/include/scratchcpp/dev/compilerlocalvariable.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "compiler.h"
+
+namespace libscratchcpp
+{
+
+class CompilerLocalVariablePrivate;
+
+/*! \brief The CompilerLocalVariable class represents a statically typed local variable in compiled code. */
+class LIBSCRATCHCPP_EXPORT CompilerLocalVariable
+{
+    public:
+        CompilerLocalVariable(CompilerValue *ptr);
+        CompilerLocalVariable(const CompilerLocalVariable &) = delete;
+
+        CompilerValue *ptr() const;
+        Compiler::StaticType type() const;
+
+    private:
+        spimpl::unique_impl_ptr<CompilerLocalVariablePrivate> impl;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index a3e2790e..9320b664 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -9,6 +9,9 @@ target_sources(scratchcpp
     compilerconstant.cpp
     compilerconstant_p.cpp
     compilerconstant_p.h
+    compilerlocalvariable.cpp
+    compilerlocalvariable_p.cpp
+    compilerlocalvariable_p.h
     executioncontext.cpp
     executioncontext_p.cpp
     executioncontext_p.h
diff --git a/src/dev/engine/compilerlocalvariable.cpp b/src/dev/engine/compilerlocalvariable.cpp
new file mode 100644
index 00000000..8a8329a4
--- /dev/null
+++ b/src/dev/engine/compilerlocalvariable.cpp
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/dev/compilerlocalvariable.h>
+#include <scratchcpp/dev/compilervalue.h>
+
+#include "compilerlocalvariable_p.h"
+
+using namespace libscratchcpp;
+
+CompilerLocalVariable::CompilerLocalVariable(CompilerValue *ptr) :
+    impl(spimpl::make_unique_impl<CompilerLocalVariablePrivate>(ptr))
+{
+}
+
+CompilerValue *CompilerLocalVariable::ptr() const
+{
+    return impl->ptr;
+}
+
+Compiler::StaticType CompilerLocalVariable::type() const
+{
+    return impl->ptr->type();
+}
diff --git a/src/dev/engine/compilerlocalvariable_p.cpp b/src/dev/engine/compilerlocalvariable_p.cpp
new file mode 100644
index 00000000..3de1c615
--- /dev/null
+++ b/src/dev/engine/compilerlocalvariable_p.cpp
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <cassert>
+
+#include "compilerlocalvariable_p.h"
+
+using namespace libscratchcpp;
+
+CompilerLocalVariablePrivate::CompilerLocalVariablePrivate(CompilerValue *ptr) :
+    ptr(ptr)
+{
+    assert(ptr);
+}
diff --git a/src/dev/engine/compilerlocalvariable_p.h b/src/dev/engine/compilerlocalvariable_p.h
new file mode 100644
index 00000000..f1872668
--- /dev/null
+++ b/src/dev/engine/compilerlocalvariable_p.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace libscratchcpp
+{
+
+class CompilerValue;
+
+struct CompilerLocalVariablePrivate
+{
+        CompilerLocalVariablePrivate(CompilerValue *ptr);
+        CompilerLocalVariablePrivate(CompilerLocalVariablePrivate &) = delete;
+
+        CompilerValue *ptr = nullptr;
+};
+
+} // namespace libscratchcpp
diff --git a/test/dev/compiler/CMakeLists.txt b/test/dev/compiler/CMakeLists.txt
index feee72ca..cdc9696c 100644
--- a/test/dev/compiler/CMakeLists.txt
+++ b/test/dev/compiler/CMakeLists.txt
@@ -3,6 +3,7 @@ add_executable(
   compiler_test.cpp
   compilervalue_test.cpp
   compilerconstant_test.cpp
+  compilerlocalvariable_test.cpp
 )
 
 target_link_libraries(
diff --git a/test/dev/compiler/compilerlocalvariable_test.cpp b/test/dev/compiler/compilerlocalvariable_test.cpp
new file mode 100644
index 00000000..27cb0a25
--- /dev/null
+++ b/test/dev/compiler/compilerlocalvariable_test.cpp
@@ -0,0 +1,33 @@
+#include <scratchcpp/dev/compilerlocalvariable.h>
+#include <scratchcpp/dev/compilervalue.h>
+#include <gtest/gtest.h>
+
+using namespace libscratchcpp;
+
+TEST(CompilerLocalVariableTest, Constructors)
+{
+    CompilerValue ptr(Compiler::StaticType::Number);
+    CompilerLocalVariable var(&ptr);
+    ASSERT_EQ(var.ptr(), &ptr);
+}
+
+TEST(CompilerLocalVariableTest, Type)
+{
+    {
+        CompilerValue ptr(Compiler::StaticType::Number);
+        CompilerLocalVariable var(&ptr);
+        ASSERT_EQ(var.type(), ptr.type());
+    }
+
+    {
+        CompilerValue ptr(Compiler::StaticType::Bool);
+        CompilerLocalVariable var(&ptr);
+        ASSERT_EQ(var.type(), ptr.type());
+    }
+
+    {
+        CompilerValue ptr(Compiler::StaticType::String);
+        CompilerLocalVariable var(&ptr);
+        ASSERT_EQ(var.type(), ptr.type());
+    }
+}

From 408404f27433f7af490a50dc270f4f6bfe6901e7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 15:53:45 +0100
Subject: [PATCH 180/259] LLVMCodeBuilder: Implement local variables

---
 src/dev/engine/internal/icodebuilder.h        |  4 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 77 +++++++++++++++++++
 .../engine/internal/llvm/llvmcodebuilder.h    |  5 ++
 .../engine/internal/llvm/llvminstruction.h    |  3 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 57 ++++++++++++++
 test/mocks/codebuildermock.h                  |  4 +
 6 files changed, 150 insertions(+)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index d42db981..a3d38629 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -24,6 +24,7 @@ class ICodeBuilder
         virtual CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0;
         virtual CompilerConstant *addConstValue(const Value &value) = 0;
         virtual CompilerValue *addLoopIndex() = 0;
+        virtual CompilerValue *addLocalVariableValue(CompilerLocalVariable *variable) = 0;
         virtual CompilerValue *addVariableValue(Variable *variable) = 0;
         virtual CompilerValue *addListContents(List *list) = 0;
         virtual CompilerValue *addListItem(List *list, CompilerValue *index) = 0;
@@ -66,6 +67,9 @@ class ICodeBuilder
 
         virtual CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) = 0;
 
+        virtual CompilerLocalVariable *createLocalVariable(Compiler::StaticType type) = 0;
+        virtual void createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value) = 0;
+
         virtual void createVariableWrite(Variable *variable, CompilerValue *value) = 0;
 
         virtual void createListClear(List *list) = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 546d5b48..a9c36b28 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -10,6 +10,7 @@
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
 #include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/dev/compilerlocalvariable.h>
 
 #include "llvmcodebuilder.h"
 #include "llvmexecutablecode.h"
@@ -511,6 +512,64 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::CreateLocalVariable: {
+                assert(step.args.empty());
+                llvm::Type *type = nullptr;
+
+                switch (step.functionReturnReg->type()) {
+                    case Compiler::StaticType::Number:
+                        type = m_builder.getDoubleTy();
+                        break;
+
+                    case Compiler::StaticType::Bool:
+                        type = m_builder.getInt1Ty();
+                        break;
+
+                    case Compiler::StaticType::String:
+                        std::cerr << "error: local variables do not support string type" << std::endl;
+                        break;
+
+                    default:
+                        assert(false);
+                        break;
+                }
+
+                step.functionReturnReg->value = m_builder.CreateAlloca(type);
+                break;
+            }
+
+            case LLVMInstruction::Type::WriteLocalVariable: {
+                assert(step.args.size() == 2);
+                const auto &arg1 = step.args[0];
+                const auto &arg2 = step.args[1];
+                llvm::Value *converted = castValue(arg2.second, arg2.first);
+                m_builder.CreateStore(converted, arg1.second->value);
+                break;
+            }
+
+            case LLVMInstruction::Type::ReadLocalVariable: {
+                assert(step.args.size() == 1);
+                const auto &arg = step.args[0];
+                llvm::Type *type = nullptr;
+
+                switch (step.functionReturnReg->type()) {
+                    case Compiler::StaticType::Number:
+                        type = m_builder.getDoubleTy();
+                        break;
+
+                    case Compiler::StaticType::Bool:
+                        type = m_builder.getInt1Ty();
+                        break;
+
+                    default:
+                        assert(false);
+                        break;
+                }
+
+                step.functionReturnReg->value = m_builder.CreateLoad(type, arg.second->value);
+                break;
+            }
+
             case LLVMInstruction::Type::WriteVariable: {
                 assert(step.args.size() == 1);
                 assert(m_variablePtrs.find(step.workVariable) != m_variablePtrs.cend());
@@ -1049,6 +1108,11 @@ CompilerValue *LLVMCodeBuilder::addLoopIndex()
     return createOp(LLVMInstruction::Type::LoopIndex, Compiler::StaticType::Number, {}, {});
 }
 
+CompilerValue *LLVMCodeBuilder::addLocalVariableValue(CompilerLocalVariable *variable)
+{
+    return createOp(LLVMInstruction::Type::ReadLocalVariable, variable->type(), variable->type(), { variable->ptr() });
+}
+
 CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
     LLVMInstruction ins(LLVMInstruction::Type::ReadVariable);
@@ -1256,6 +1320,19 @@ CompilerValue *LLVMCodeBuilder::createSelect(CompilerValue *cond, CompilerValue
     return createOp(LLVMInstruction::Type::Select, valueType, { Compiler::StaticType::Bool, valueType, valueType }, { cond, trueValue, falseValue });
 }
 
+CompilerLocalVariable *LLVMCodeBuilder::createLocalVariable(Compiler::StaticType type)
+{
+    CompilerValue *ptr = createOp(LLVMInstruction::Type::CreateLocalVariable, type);
+    auto var = std::make_shared<CompilerLocalVariable>(ptr);
+    m_localVars.push_back(var);
+    return var.get();
+}
+
+void LLVMCodeBuilder::createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value)
+{
+    createOp(LLVMInstruction::Type::WriteLocalVariable, Compiler::StaticType::Void, variable->type(), { variable->ptr(), value });
+}
+
 void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value)
 {
     LLVMInstruction ins(LLVMInstruction::Type::WriteVariable);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index bd18635f..afd21005 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -31,6 +31,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override;
         CompilerConstant *addConstValue(const Value &value) override;
         CompilerValue *addLoopIndex() override;
+        CompilerValue *addLocalVariableValue(CompilerLocalVariable *variable) override;
         CompilerValue *addVariableValue(Variable *variable) override;
         CompilerValue *addListContents(List *list) override;
         CompilerValue *addListItem(List *list, CompilerValue *index) override;
@@ -73,6 +74,9 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) override;
 
+        CompilerLocalVariable *createLocalVariable(Compiler::StaticType type) override;
+        void createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value) override;
+
         void createVariableWrite(Variable *variable, CompilerValue *value) override;
 
         void createListClear(List *list) override;
@@ -194,6 +198,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         std::vector<LLVMInstruction> m_instructions;
         std::vector<std::shared_ptr<LLVMRegister>> m_regs;
+        std::vector<std::shared_ptr<CompilerLocalVariable>> m_localVars;
         bool m_defaultWarp = false;
         bool m_warp = false;
 
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 35034cca..f4518142 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -43,6 +43,9 @@ struct LLVMInstruction
             Exp,
             Exp10,
             Select,
+            CreateLocalVariable,
+            WriteLocalVariable,
+            ReadLocalVariable,
             WriteVariable,
             ReadVariable,
             ClearList,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index fae22ef0..a71e4152 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1519,6 +1519,63 @@ TEST_F(LLVMCodeBuilderTest, Exp10)
     runUnaryNumOpTest(OpType::Exp10, nan, 1.0);
 }
 
+TEST_F(LLVMCodeBuilderTest, LocalVariables)
+{
+    EngineMock engine;
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&engine);
+    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+
+    createBuilder(&sprite, true);
+
+    CompilerLocalVariable *var1 = m_builder->createLocalVariable(Compiler::StaticType::Number);
+    CompilerLocalVariable *var2 = m_builder->createLocalVariable(Compiler::StaticType::Number);
+    CompilerLocalVariable *var3 = m_builder->createLocalVariable(Compiler::StaticType::Bool);
+    CompilerLocalVariable *var4 = m_builder->createLocalVariable(Compiler::StaticType::Bool);
+
+    CompilerValue *v = m_builder->addConstValue(5);
+    m_builder->createLocalVariableWrite(var1, v);
+
+    v = m_builder->addConstValue(-23.5);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->createLocalVariableWrite(var2, v);
+
+    v = m_builder->addConstValue(5.2);
+    v = callConstFuncForType(ValueType::Number, v);
+    m_builder->createLocalVariableWrite(var2, v);
+
+    v = m_builder->addConstValue(false);
+    m_builder->createLocalVariableWrite(var3, v);
+
+    v = m_builder->addConstValue(true);
+    m_builder->createLocalVariableWrite(var3, v);
+
+    v = m_builder->addConstValue(false);
+    v = callConstFuncForType(ValueType::Bool, v);
+    m_builder->createLocalVariableWrite(var4, v);
+
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var1) });
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var2) });
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var3) });
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var4) });
+
+    static const std::string expected =
+        "5\n"
+        "5.2\n"
+        "true\n"
+        "false\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, WriteVariable)
 {
     EngineMock engine;
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 674abc8d..2862bea8 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -14,6 +14,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(CompilerValue *, addFunctionCallWithCtx, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override));
         MOCK_METHOD(CompilerConstant *, addConstValue, (const Value &), (override));
         MOCK_METHOD(CompilerValue *, addLoopIndex, (), (override));
+        MOCK_METHOD(CompilerValue *, addLocalVariableValue, (CompilerLocalVariable *), (override));
         MOCK_METHOD(CompilerValue *, addVariableValue, (Variable *), (override));
         MOCK_METHOD(CompilerValue *, addListContents, (List *), (override));
         MOCK_METHOD(CompilerValue *, addListItem, (List *, CompilerValue *), (override));
@@ -56,6 +57,9 @@ class CodeBuilderMock : public ICodeBuilder
 
         MOCK_METHOD(CompilerValue *, createSelect, (CompilerValue *, CompilerValue *, CompilerValue *, Compiler::StaticType), (override));
 
+        MOCK_METHOD(CompilerLocalVariable *, createLocalVariable, (Compiler::StaticType), (override));
+        MOCK_METHOD(void, createLocalVariableWrite, (CompilerLocalVariable *, CompilerValue *), (override));
+
         MOCK_METHOD(void, createVariableWrite, (Variable *, CompilerValue *), (override));
 
         MOCK_METHOD(void, createListClear, (List *), (override));

From 8b5f06a48ad026de8d19e79aaff6f1e953427831 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 15:56:24 +0100
Subject: [PATCH 181/259] Compiler: Add createRandomInt() method

---
 include/scratchcpp/dev/compiler.h   |  1 +
 src/dev/engine/compiler.cpp         |  9 +++++++++
 test/dev/compiler/compiler_test.cpp | 18 ++++++++++++++++++
 3 files changed, 28 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index eb70df4d..1cc06d3d 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -66,6 +66,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2);
 
         CompilerValue *createRandom(CompilerValue *from, CompilerValue *to);
+        CompilerValue *createRandomInt(CompilerValue *from, CompilerValue *to);
 
         CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 807fa11f..1820e45a 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -199,6 +199,15 @@ CompilerValue *Compiler::createRandom(CompilerValue *from, CompilerValue *to)
     return impl->builder->createRandom(from, to);
 }
 
+/*!
+ * Creates a random integer instruction.
+ * \note Infinity or NaN results in undefined behavior.
+ */
+CompilerValue *Compiler::createRandomInt(CompilerValue *from, CompilerValue *to)
+{
+    return impl->builder->createRandomInt(from, to);
+}
+
 /*! Creates an equality comparison instruction. */
 CompilerValue *Compiler::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index c2abc8d2..7e0d09c9 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -484,6 +484,24 @@ TEST_F(CompilerTest, CreateRandom)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CreateRandomInt)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createRandomInt(&arg1, &arg2)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createRandomInt(&arg1, &arg2), &ret);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, CreateCmpEQ)
 {
     Compiler compiler(&m_engine, &m_target);

From 8fbb31c799cb682ea70f2ae026c2920288e9806c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 16:09:48 +0100
Subject: [PATCH 182/259] Compiler: Add methods for local variables

---
 include/scratchcpp/dev/compiler.h   |  5 +++
 src/dev/engine/compiler.cpp         | 18 ++++++++
 test/dev/compiler/compiler_test.cpp | 64 +++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 1cc06d3d..4486099e 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -16,6 +16,7 @@ class Target;
 class ExecutableCode;
 class CompilerValue;
 class CompilerConstant;
+class CompilerLocalVariable;
 class Variable;
 class List;
 class Input;
@@ -52,6 +53,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *addFunctionCallWithCtx(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
         CompilerConstant *addConstValue(const Value &value);
         CompilerValue *addLoopIndex();
+        CompilerValue *addLocalVariableValue(CompilerLocalVariable *variable);
         CompilerValue *addVariableValue(Variable *variable);
         CompilerValue *addListContents(List *list);
         CompilerValue *addListItem(List *list, CompilerValue *index);
@@ -95,6 +97,9 @@ class LIBSCRATCHCPP_EXPORT Compiler
 
         CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType);
 
+        CompilerLocalVariable *createLocalVariable(Compiler::StaticType type);
+        void createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value);
+
         void createVariableWrite(Variable *variable, CompilerValue *value);
 
         void createListClear(List *list);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 1820e45a..bcbc8a35 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -127,6 +127,12 @@ CompilerValue *Compiler::addLoopIndex()
     return impl->builder->addLoopIndex();
 }
 
+/*! Adds the value of the given local variable to the code. */
+CompilerValue *Compiler::addLocalVariableValue(CompilerLocalVariable *variable)
+{
+    return impl->builder->addLocalVariableValue(variable);
+}
+
 /*! Adds the value of the given variable to the code. */
 CompilerValue *Compiler::addVariableValue(Variable *variable)
 {
@@ -346,6 +352,18 @@ CompilerValue *Compiler::createSelect(CompilerValue *cond, CompilerValue *trueVa
     return impl->builder->createSelect(cond, trueValue, falseValue, valueType);
 }
 
+/*! Creates a local variable with the given type. */
+CompilerLocalVariable *Compiler::createLocalVariable(StaticType type)
+{
+    return impl->builder->createLocalVariable(type);
+}
+
+/*! Creates a local variable write operation. */
+void Compiler::createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value)
+{
+    impl->builder->createLocalVariableWrite(variable, value);
+}
+
 /*! Creates a variable write operation. */
 void Compiler::createVariableWrite(Variable *variable, CompilerValue *value)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 7e0d09c9..945e2d0d 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -1,5 +1,6 @@
 #include <scratchcpp/dev/compiler.h>
 #include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/dev/compilerlocalvariable.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
@@ -180,6 +181,29 @@ TEST_F(CompilerTest, AddLoopIndex)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, AddLocalVariableValue)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Number);
+        CompilerValue ptr1(Compiler::StaticType::Number);
+        CompilerValue ptr2(Compiler::StaticType::Bool);
+        CompilerLocalVariable var1(&ptr1);
+        CompilerLocalVariable var2(&ptr2);
+
+        EXPECT_CALL(*m_builder, addLocalVariableValue(&var1)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addLocalVariableValue(&var1), &ret);
+
+        EXPECT_CALL(*m_builder, addLocalVariableValue(&var2)).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addLocalVariableValue(&var2), nullptr);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, AddVariableValue)
 {
     Compiler compiler(&m_engine, &m_target);
@@ -905,6 +929,46 @@ TEST_F(CompilerTest, CreateSelect)
     compile(compiler, block);
 }
 
+TEST_F(CompilerTest, CreateLocalVariable)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ptr1(Compiler::StaticType::Number);
+        CompilerLocalVariable var1(&ptr1);
+        EXPECT_CALL(*m_builder, createLocalVariable(var1.type())).WillOnce(Return(&var1));
+        EXPECT_EQ(compiler->createLocalVariable(var1.type()), &var1);
+
+        CompilerValue ptr2(Compiler::StaticType::Number);
+        CompilerLocalVariable var2(&ptr2);
+        EXPECT_CALL(*m_builder, createLocalVariable(var2.type())).WillOnce(Return(&var2));
+        EXPECT_EQ(compiler->createLocalVariable(var2.type()), &var2);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
+TEST_F(CompilerTest, CreateLocalVariableWrite)
+{
+    Compiler compiler(&m_engine, &m_target);
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ptr(Compiler::StaticType::Number);
+        CompilerLocalVariable var(&ptr);
+        CompilerValue arg(Compiler::StaticType::Number);
+        EXPECT_CALL(*m_builder, createLocalVariableWrite(&var, &arg));
+        compiler->createLocalVariableWrite(&var, &arg);
+
+        return nullptr;
+    });
+
+    compile(compiler, block);
+}
+
 TEST_F(CompilerTest, CreateVariableWrite)
 {
     Compiler compiler(&m_engine, &m_target);

From 545df0c0c0ca630b19fd986e87c8622cce45f609 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 16:10:41 +0100
Subject: [PATCH 183/259] Implement data_deleteoflist

---
 src/dev/blocks/listblocks.cpp        | 56 +++++++++++++++++++++
 src/dev/blocks/listblocks.h          |  5 ++
 test/dev/blocks/list_blocks_test.cpp | 74 ++++++++++++++++++++++++++++
 3 files changed, 135 insertions(+)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index a6999704..ad357a33 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -23,6 +23,7 @@ std::string ListBlocks::description() const
 void ListBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "data_addtolist", &compileAddToList);
+    engine->addCompileFunction(this, "data_deleteoflist", &compileDeleteOfList);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -35,3 +36,58 @@ CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::getListIndex(Compiler *compiler, CompilerValue *input, List *list)
+{
+    CompilerLocalVariable *ret = compiler->createLocalVariable(Compiler::StaticType::Number);
+    CompilerValue *size = compiler->addListSize(list);
+
+    CompilerValue *isRandom1 = compiler->createCmpEQ(input, compiler->addConstValue("random"));
+    CompilerValue *isRandom2 = compiler->createCmpEQ(input, compiler->addConstValue("any"));
+    CompilerValue *isRandom = compiler->createOr(isRandom1, isRandom2);
+
+    compiler->beginIfStatement(isRandom);
+    {
+        CompilerValue *random = compiler->createRandomInt(compiler->addConstValue(1), size);
+        compiler->createLocalVariableWrite(ret, random);
+    }
+    compiler->beginElseBranch();
+    {
+        CompilerValue *isLast = compiler->createCmpEQ(input, compiler->addConstValue("last"));
+        compiler->createLocalVariableWrite(ret, compiler->createSelect(isLast, size, input, Compiler::StaticType::Number));
+    }
+    compiler->endIf();
+
+    return compiler->addLocalVariableValue(ret);
+}
+
+CompilerValue *ListBlocks::compileDeleteOfList(Compiler *compiler)
+{
+    List *list = static_cast<List *>(compiler->field("LIST")->valuePtr().get());
+    assert(list);
+
+    if (list) {
+        CompilerValue *index = compiler->addInput("INDEX");
+        CompilerValue *cond = compiler->createCmpEQ(index, compiler->addConstValue("all"));
+        compiler->beginIfStatement(cond);
+        {
+            compiler->createListClear(list);
+        }
+        compiler->beginElseBranch();
+        {
+            index = getListIndex(compiler, index, list);
+            index = compiler->createSub(index, compiler->addConstValue(1));
+            CompilerValue *min = compiler->addConstValue(-1);
+            CompilerValue *max = compiler->addListSize(list);
+            cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
+            compiler->beginIfStatement(cond);
+            {
+                compiler->createListRemove(list, index);
+            }
+            compiler->endIf();
+        }
+        compiler->endIf();
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index 9163b635..cace3dc7 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -7,6 +7,8 @@
 namespace libscratchcpp
 {
 
+class List;
+
 class ListBlocks : public IExtension
 {
     public:
@@ -17,6 +19,9 @@ class ListBlocks : public IExtension
 
     private:
         static CompilerValue *compileAddToList(Compiler *compiler);
+        static CompilerValue *getListIndex(Compiler *compiler, CompilerValue *input, List *list);
+        static CompilerValue *compileDeleteOfList(Compiler *compiler);
+        static CompilerValue *compileDeleteAllOfList(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index b18c169a..8ddb4ac4 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -3,7 +3,12 @@
 #include <scratchcpp/list.h>
 #include <scratchcpp/dev/compiler.h>
 #include <scratchcpp/dev/test/scriptbuilder.h>
+#include <scratchcpp/script.h>
+#include <scratchcpp/thread.h>
+#include <scratchcpp/dev/executioncontext.h>
+#include <scratchcpp/dev/executablecode.h>
 #include <enginemock.h>
+#include <randomgeneratormock.h>
 
 #include "../common.h"
 #include "dev/blocks/listblocks.h"
@@ -11,6 +16,8 @@
 using namespace libscratchcpp;
 using namespace libscratchcpp::test;
 
+using ::testing::Return;
+
 class ListBlocksTest : public testing::Test
 {
     public:
@@ -25,6 +32,7 @@ class ListBlocksTest : public testing::Test
         Project m_project;
         IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
+        RandomGeneratorMock m_rng;
 };
 
 TEST_F(ListBlocksTest, AddToList)
@@ -59,3 +67,69 @@ TEST_F(ListBlocksTest, AddToList)
     ASSERT_EQ(list1->toString(), "test true");
     ASSERT_EQ(list2->toString(), "123 Hello world");
 }
+
+TEST_F(ListBlocksTest, DeleteOfList)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list1 = std::make_shared<List>("", "");
+    list1->append("Lorem");
+    list1->append("ipsum");
+    list1->append("dolor");
+    list1->append(123);
+    list1->append(true);
+    target->addList(list1);
+
+    auto list2 = std::make_shared<List>("", "");
+    list2->append("Hello");
+    list2->append("world");
+    list2->append(false);
+    list2->append(-543.5);
+    list2->append("abc");
+    list2->append(52.4);
+    target->addList(list2);
+
+    auto list3 = std::make_shared<List>("", "");
+    list3->append(1);
+    list3->append(2);
+    list3->append(3);
+    target->addList(list3);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addTest = [&builder](const Value &index, std::shared_ptr<List> list) {
+        builder.addBlock("data_deleteoflist");
+        builder.addValueInput("INDEX", index);
+        builder.addEntityField("LIST", list);
+        return builder.currentBlock();
+    };
+
+    auto block = addTest(1, list1);
+    addTest(3, list1);
+    addTest(2, list1);
+    addTest(0, list1);
+    addTest(3, list1);
+
+    addTest("last", list2);
+    addTest("random", list2);
+    addTest("any", list2);
+
+    addTest("all", list3);
+
+    builder.build();
+
+    Compiler compiler(&m_engineMock, target.get());
+    auto code = compiler.compile(block);
+    Script script(target.get(), block, &m_engineMock);
+    script.setCode(code);
+    Thread thread(target.get(), &m_engineMock, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    ctx->setRng(&m_rng);
+
+    EXPECT_CALL(m_rng, randint(1, 5)).WillOnce(Return(2));
+    EXPECT_CALL(m_rng, randint(1, 4)).WillOnce(Return(3));
+    code->run(ctx.get());
+    ASSERT_EQ(list1->toString(), "ipsum true");
+    ASSERT_EQ(list2->toString(), "Hello false abc");
+    ASSERT_TRUE(list3->empty());
+}

From b976f53d7bf768d20bf77e02ac401bec95e2ee6f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 16:16:42 +0100
Subject: [PATCH 184/259] Implement data_deletealloflist

---
 src/dev/blocks/listblocks.cpp        | 12 ++++++++++++
 test/dev/blocks/list_blocks_test.cpp | 21 +++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index ad357a33..a27bbf30 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -24,6 +24,7 @@ void ListBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "data_addtolist", &compileAddToList);
     engine->addCompileFunction(this, "data_deleteoflist", &compileDeleteOfList);
+    engine->addCompileFunction(this, "data_deletealloflist", &compileDeleteAllOfList);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -91,3 +92,14 @@ CompilerValue *ListBlocks::compileDeleteOfList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::compileDeleteAllOfList(Compiler *compiler)
+{
+    auto list = compiler->field("LIST")->valuePtr();
+    assert(list);
+
+    if (list)
+        compiler->createListClear(static_cast<List *>(list.get()));
+
+    return nullptr;
+}
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index 8ddb4ac4..441e29b4 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -133,3 +133,24 @@ TEST_F(ListBlocksTest, DeleteOfList)
     ASSERT_EQ(list2->toString(), "Hello false abc");
     ASSERT_TRUE(list3->empty());
 }
+
+TEST_F(ListBlocksTest, DeleteAllOfList)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list = std::make_shared<List>("", "");
+    list->append("Lorem");
+    list->append("ipsum");
+    list->append("dolor");
+    list->append(123);
+    list->append(true);
+    target->addList(list);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+    builder.addBlock("data_deletealloflist");
+    builder.addEntityField("LIST", list);
+    builder.build();
+
+    builder.run();
+    ASSERT_TRUE(list->empty());
+}

From fbc2643ba1a1905c8a364406f1db64ac8dbe9428 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 17:07:37 +0100
Subject: [PATCH 185/259] Implement data_insertatlist

---
 src/dev/blocks/listblocks.cpp        | 35 ++++++++++++++---
 src/dev/blocks/listblocks.h          |  3 +-
 test/dev/blocks/list_blocks_test.cpp | 57 ++++++++++++++++++++++++++++
 3 files changed, 88 insertions(+), 7 deletions(-)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index a27bbf30..e970e2cc 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -25,6 +25,7 @@ void ListBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "data_addtolist", &compileAddToList);
     engine->addCompileFunction(this, "data_deleteoflist", &compileDeleteOfList);
     engine->addCompileFunction(this, "data_deletealloflist", &compileDeleteAllOfList);
+    engine->addCompileFunction(this, "data_insertatlist", &compileInsertAtList);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -38,10 +39,9 @@ CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
     return nullptr;
 }
 
-CompilerValue *ListBlocks::getListIndex(Compiler *compiler, CompilerValue *input, List *list)
+CompilerValue *ListBlocks::getListIndex(Compiler *compiler, CompilerValue *input, List *list, CompilerValue *listSize)
 {
     CompilerLocalVariable *ret = compiler->createLocalVariable(Compiler::StaticType::Number);
-    CompilerValue *size = compiler->addListSize(list);
 
     CompilerValue *isRandom1 = compiler->createCmpEQ(input, compiler->addConstValue("random"));
     CompilerValue *isRandom2 = compiler->createCmpEQ(input, compiler->addConstValue("any"));
@@ -49,13 +49,13 @@ CompilerValue *ListBlocks::getListIndex(Compiler *compiler, CompilerValue *input
 
     compiler->beginIfStatement(isRandom);
     {
-        CompilerValue *random = compiler->createRandomInt(compiler->addConstValue(1), size);
+        CompilerValue *random = compiler->createRandomInt(compiler->addConstValue(1), listSize);
         compiler->createLocalVariableWrite(ret, random);
     }
     compiler->beginElseBranch();
     {
         CompilerValue *isLast = compiler->createCmpEQ(input, compiler->addConstValue("last"));
-        compiler->createLocalVariableWrite(ret, compiler->createSelect(isLast, size, input, Compiler::StaticType::Number));
+        compiler->createLocalVariableWrite(ret, compiler->createSelect(isLast, listSize, input, Compiler::StaticType::Number));
     }
     compiler->endIf();
 
@@ -76,10 +76,10 @@ CompilerValue *ListBlocks::compileDeleteOfList(Compiler *compiler)
         }
         compiler->beginElseBranch();
         {
-            index = getListIndex(compiler, index, list);
-            index = compiler->createSub(index, compiler->addConstValue(1));
             CompilerValue *min = compiler->addConstValue(-1);
             CompilerValue *max = compiler->addListSize(list);
+            index = getListIndex(compiler, index, list, max);
+            index = compiler->createSub(index, compiler->addConstValue(1));
             cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
             compiler->beginIfStatement(cond);
             {
@@ -103,3 +103,26 @@ CompilerValue *ListBlocks::compileDeleteAllOfList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::compileInsertAtList(Compiler *compiler)
+{
+    List *list = static_cast<List *>(compiler->field("LIST")->valuePtr().get());
+    assert(list);
+
+    if (list) {
+        CompilerValue *index = compiler->addInput("INDEX");
+        CompilerValue *min = compiler->addConstValue(-1);
+        CompilerValue *max = compiler->createAdd(compiler->addListSize(list), compiler->addConstValue(1));
+        index = getListIndex(compiler, index, list, max);
+        index = compiler->createSub(index, compiler->addConstValue(1));
+        CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
+        compiler->beginIfStatement(cond);
+        {
+            CompilerValue *item = compiler->addInput("ITEM");
+            compiler->createListInsert(list, index, item);
+        }
+        compiler->endIf();
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index cace3dc7..5589dda8 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -19,9 +19,10 @@ class ListBlocks : public IExtension
 
     private:
         static CompilerValue *compileAddToList(Compiler *compiler);
-        static CompilerValue *getListIndex(Compiler *compiler, CompilerValue *input, List *list);
+        static CompilerValue *getListIndex(Compiler *compiler, CompilerValue *input, List *list, CompilerValue *listSize);
         static CompilerValue *compileDeleteOfList(Compiler *compiler);
         static CompilerValue *compileDeleteAllOfList(Compiler *compiler);
+        static CompilerValue *compileInsertAtList(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index 441e29b4..e7f04069 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -154,3 +154,60 @@ TEST_F(ListBlocksTest, DeleteAllOfList)
     builder.run();
     ASSERT_TRUE(list->empty());
 }
+
+TEST_F(ListBlocksTest, InsertAtList)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list1 = std::make_shared<List>("", "");
+    list1->append("Lorem");
+    list1->append("ipsum");
+    list1->append("dolor");
+    list1->append(123);
+    list1->append(true);
+    target->addList(list1);
+
+    auto list2 = std::make_shared<List>("", "");
+    list2->append("Hello");
+    list2->append("world");
+    list2->append(false);
+    list2->append(-543.5);
+    list2->append("abc");
+    list2->append(52.4);
+    target->addList(list2);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addTest = [&builder](const Value &index, const Value &item, std::shared_ptr<List> list) {
+        builder.addBlock("data_insertatlist");
+        builder.addValueInput("ITEM", item);
+        builder.addValueInput("INDEX", index);
+        builder.addEntityField("LIST", list);
+        return builder.currentBlock();
+    };
+
+    auto block = addTest(4, "sit", list1);
+    addTest(7, false, list1);
+    addTest(0, "test", list1);
+    addTest(9, "test", list1);
+
+    addTest("last", "lorem", list2);
+    addTest("random", "ipsum", list2);
+    addTest("any", "dolor", list2);
+
+    builder.build();
+
+    Compiler compiler(&m_engineMock, target.get());
+    auto code = compiler.compile(block);
+    Script script(target.get(), block, &m_engineMock);
+    script.setCode(code);
+    Thread thread(target.get(), &m_engineMock, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    ctx->setRng(&m_rng);
+
+    EXPECT_CALL(m_rng, randint(1, 8)).WillOnce(Return(8));
+    EXPECT_CALL(m_rng, randint(1, 9)).WillOnce(Return(3));
+    code->run(ctx.get());
+    ASSERT_EQ(list1->toString(), "Lorem ipsum dolor sit 123 true false");
+    ASSERT_EQ(list2->toString(), "Hello world dolor false -543.5 abc 52.4 lorem ipsum");
+}

From 08b233f69fd75cf9d3ef566ad07d91f7f02a6e1c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 28 Dec 2024 17:25:55 +0100
Subject: [PATCH 186/259] Implement data_replaceitemoflist

---
 src/dev/blocks/listblocks.cpp        | 24 ++++++++++++
 src/dev/blocks/listblocks.h          |  1 +
 test/dev/blocks/list_blocks_test.cpp | 56 ++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index e970e2cc..f00a9b41 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -26,6 +26,7 @@ void ListBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "data_deleteoflist", &compileDeleteOfList);
     engine->addCompileFunction(this, "data_deletealloflist", &compileDeleteAllOfList);
     engine->addCompileFunction(this, "data_insertatlist", &compileInsertAtList);
+    engine->addCompileFunction(this, "data_replaceitemoflist", &compileReplaceItemOfList);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -126,3 +127,26 @@ CompilerValue *ListBlocks::compileInsertAtList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::compileReplaceItemOfList(Compiler *compiler)
+{
+    List *list = static_cast<List *>(compiler->field("LIST")->valuePtr().get());
+    assert(list);
+
+    if (list) {
+        CompilerValue *index = compiler->addInput("INDEX");
+        CompilerValue *min = compiler->addConstValue(-1);
+        CompilerValue *max = compiler->addListSize(list);
+        index = getListIndex(compiler, index, list, max);
+        index = compiler->createSub(index, compiler->addConstValue(1));
+        CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
+        compiler->beginIfStatement(cond);
+        {
+            CompilerValue *item = compiler->addInput("ITEM");
+            compiler->createListReplace(list, index, item);
+        }
+        compiler->endIf();
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index 5589dda8..4f4e4aa4 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -23,6 +23,7 @@ class ListBlocks : public IExtension
         static CompilerValue *compileDeleteOfList(Compiler *compiler);
         static CompilerValue *compileDeleteAllOfList(Compiler *compiler);
         static CompilerValue *compileInsertAtList(Compiler *compiler);
+        static CompilerValue *compileReplaceItemOfList(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index e7f04069..69c12229 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -211,3 +211,59 @@ TEST_F(ListBlocksTest, InsertAtList)
     ASSERT_EQ(list1->toString(), "Lorem ipsum dolor sit 123 true false");
     ASSERT_EQ(list2->toString(), "Hello world dolor false -543.5 abc 52.4 lorem ipsum");
 }
+
+TEST_F(ListBlocksTest, ReplaceItemOfList)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list1 = std::make_shared<List>("", "");
+    list1->append("Lorem");
+    list1->append("ipsum");
+    list1->append("dolor");
+    list1->append(123);
+    list1->append(true);
+    target->addList(list1);
+
+    auto list2 = std::make_shared<List>("", "");
+    list2->append("Hello");
+    list2->append("world");
+    list2->append(false);
+    list2->append(-543.5);
+    list2->append("abc");
+    list2->append(52.4);
+    target->addList(list2);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addTest = [&builder](const Value &index, const Value &item, std::shared_ptr<List> list) {
+        builder.addBlock("data_replaceitemoflist");
+        builder.addValueInput("INDEX", index);
+        builder.addEntityField("LIST", list);
+        builder.addValueInput("ITEM", item);
+        return builder.currentBlock();
+    };
+
+    auto block = addTest(4, "sit", list1);
+    addTest(5, -53.18, list1);
+    addTest(0, "test", list1);
+    addTest(6, "test", list1);
+
+    addTest("last", "lorem", list2);
+    addTest("random", "ipsum", list2);
+    addTest("any", "dolor", list2);
+
+    builder.build();
+
+    Compiler compiler(&m_engineMock, target.get());
+    auto code = compiler.compile(block);
+    Script script(target.get(), block, &m_engineMock);
+    script.setCode(code);
+    Thread thread(target.get(), &m_engineMock, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    ctx->setRng(&m_rng);
+
+    EXPECT_CALL(m_rng, randint(1, 6)).WillOnce(Return(4)).WillOnce(Return(1));
+    code->run(ctx.get());
+    ASSERT_EQ(list1->toString(), "Lorem ipsum dolor sit -53.18");
+    ASSERT_EQ(list2->toString(), "dolor world false ipsum abc lorem");
+}

From 297ae3a145820838fc125dcc7d99672995b257dc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 00:17:50 +0100
Subject: [PATCH 187/259] LLVMCodeBuilder: Add support for unknown type in
 select

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 29 ++++++++++++++-----
 .../engine/internal/llvm/llvmcodebuilder.h    |  6 ++--
 test/dev/llvm/llvmcodebuilder_test.cpp        | 13 ++++++++-
 3 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index a9c36b28..e269eec0 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -505,9 +505,19 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
                 const auto &arg3 = step.args[2];
+                auto type = arg2.first;
                 llvm::Value *cond = castValue(arg1.second, arg1.first);
-                llvm::Value *trueValue = castValue(arg2.second, arg2.first);
-                llvm::Value *falseValue = castValue(arg3.second, arg3.first);
+                llvm::Value *trueValue;
+                llvm::Value *falseValue;
+
+                if (type == Compiler::StaticType::Unknown) {
+                    trueValue = createValue(arg2.second);
+                    falseValue = createValue(arg3.second);
+                } else {
+                    trueValue = castValue(arg2.second, type);
+                    falseValue = castValue(arg3.second, type);
+                }
+
                 step.functionReturnReg->value = m_builder.CreateSelect(cond, trueValue, falseValue);
                 break;
             }
@@ -1100,7 +1110,7 @@ CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value)
 {
     auto constReg = std::make_shared<LLVMConstantRegister>(TYPE_MAP[value.type()], value);
     auto reg = std::reinterpret_pointer_cast<LLVMRegister>(constReg);
-    return static_cast<CompilerConstant *>(addReg(reg));
+    return static_cast<CompilerConstant *>(static_cast<CompilerValue *>(addReg(reg)));
 }
 
 CompilerValue *LLVMCodeBuilder::addLoopIndex()
@@ -1317,7 +1327,12 @@ CompilerValue *LLVMCodeBuilder::createExp10(CompilerValue *num)
 
 CompilerValue *LLVMCodeBuilder::createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType)
 {
-    return createOp(LLVMInstruction::Type::Select, valueType, { Compiler::StaticType::Bool, valueType, valueType }, { cond, trueValue, falseValue });
+    LLVMRegister *ret = createOp(LLVMInstruction::Type::Select, valueType, { Compiler::StaticType::Bool, valueType, valueType }, { cond, trueValue, falseValue });
+
+    if (valueType == Compiler::StaticType::Unknown)
+        ret->isRawValue = false;
+
+    return ret;
 }
 
 CompilerLocalVariable *LLVMCodeBuilder::createLocalVariable(Compiler::StaticType type)
@@ -1569,7 +1584,7 @@ void LLVMCodeBuilder::optimize()
     modulePassManager.run(*m_module, moduleAnalysisManager);
 }
 
-CompilerValue *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg)
+LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg)
 {
     m_regs.push_back(reg);
     return reg.get();
@@ -1875,7 +1890,7 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr, llvm::Functi
     m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
 }
 
-CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
+LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
 {
     std::vector<Compiler::StaticType> types;
     types.reserve(args.size());
@@ -1886,7 +1901,7 @@ CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::S
     return createOp(ins, retType, types, args);
 }
 
-CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
+LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
 {
     m_instructions.push_back(ins);
     LLVMInstruction &createdIns = m_instructions.back();
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index afd21005..2d34f01d 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -116,7 +116,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void verifyFunction(llvm::Function *func);
         void optimize();
 
-        CompilerValue *addReg(std::shared_ptr<LLVMRegister> reg);
+        LLVMRegister *addReg(std::shared_ptr<LLVMRegister> reg);
 
         void freeHeap();
         llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType);
@@ -134,8 +134,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         void reloadLists();
         void updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func);
 
-        CompilerValue *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
-        CompilerValue *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
+        LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
+        LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
 
         void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
         void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index a71e4152..a4afc9b1 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1694,6 +1694,15 @@ TEST_F(LLVMCodeBuilderTest, Select)
     v = m_builder->createSelect(v, m_builder->addConstValue(1), m_builder->addConstValue("false"), Compiler::StaticType::Bool);
     m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
 
+    // Unknown types
+    v = m_builder->addConstValue(true);
+    v = m_builder->createSelect(v, m_builder->addConstValue("test"), m_builder->addConstValue(-456.2), Compiler::StaticType::Unknown);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    v = m_builder->addConstValue(false);
+    v = m_builder->createSelect(v, m_builder->addConstValue("abc"), m_builder->addConstValue(true), Compiler::StaticType::Unknown);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
     static const std::string expected =
         "5.8\n"
         "-17.42\n"
@@ -1704,7 +1713,9 @@ TEST_F(LLVMCodeBuilderTest, Select)
         "543\n"
         "0\n"
         "1\n"
-        "0\n";
+        "0\n"
+        "test\n"
+        "true\n";
 
     auto code = m_builder->finalize();
     testing::internal::CaptureStdout();

From 3380e7c93450f6ab5cde04f2b0f5e925844f4af7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 13:42:34 +0100
Subject: [PATCH 188/259] LLVMCodeBuilder: Fix use after free when accessing
 from another scope

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 53 ++++++++++++-------
 .../engine/internal/llvm/llvmcodebuilder.h    |  5 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 35 +++++++++++-
 3 files changed, 71 insertions(+), 22 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index e269eec0..98d4190b 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -146,7 +146,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     step.functionReturnReg->value = ret;
 
                     if (step.functionReturnReg->type() == Compiler::StaticType::String)
-                        m_heap.push_back(step.functionReturnReg->value);
+                        freeLater(step.functionReturnReg->value);
                 }
 
                 break;
@@ -749,7 +749,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 0);
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *ptr = m_builder.CreateCall(resolve_list_to_string(), listPtr.ptr);
-                m_heap.push_back(ptr); // deallocate later
+                freeLater(ptr);
                 step.functionReturnReg->value = ptr;
                 break;
             }
@@ -791,7 +791,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
             case LLVMInstruction::Type::Yield:
                 if (!m_warp) {
-                    freeHeap();
+                    // TODO: Do not allow use after suspend (use after free)
+                    freeScopeHeap();
                     syncVariables(targetVariables);
                     coro->createSuspend();
                     reloadVariables(targetVariables);
@@ -809,7 +810,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &reg = step.args[0];
                 assert(reg.first == Compiler::StaticType::Bool);
-                freeHeap();
                 statement.condition = castValue(reg.second, reg.first);
 
                 // Switch to body branch
@@ -836,7 +836,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // Jump to the branch after the if statement
                 assert(!statement.afterIf);
                 statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func);
-                freeHeap();
+                freeScopeHeap();
                 m_builder.CreateBr(statement.afterIf);
 
                 // Create else branch
@@ -855,12 +855,12 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::EndIf: {
                 assert(!ifStatements.empty());
                 LLVMIfStatement &statement = ifStatements.back();
+                freeScopeHeap();
 
                 // Jump to the branch after the if statement
                 if (!statement.afterIf)
                     statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func);
 
-                freeHeap();
                 m_builder.CreateBr(statement.afterIf);
 
                 if (statement.elseBranch) {
@@ -901,7 +901,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Clamp count if <= 0 (we can skip the loop if count is not positive)
                 llvm::Value *comparison = m_builder.CreateFCmpULE(count, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)));
-                freeHeap();
                 m_builder.CreateCondBr(comparison, loop.afterLoop, roundBranch);
 
                 // Round (Scratch-specific behavior)
@@ -955,7 +954,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &reg = step.args[0];
                 assert(reg.first == Compiler::StaticType::Bool);
                 llvm::Value *condition = castValue(reg.second, reg.first);
-                freeHeap();
                 m_builder.CreateCondBr(condition, body, loop.afterLoop);
 
                 // Switch to body branch
@@ -977,7 +975,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &reg = step.args[0];
                 assert(reg.first == Compiler::StaticType::Bool);
                 llvm::Value *condition = castValue(reg.second, reg.first);
-                freeHeap();
                 m_builder.CreateCondBr(condition, loop.afterLoop, body);
 
                 // Switch to body branch
@@ -990,7 +987,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMLoop loop;
                 loop.isRepeatLoop = false;
                 loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func);
-                freeHeap();
                 m_builder.CreateBr(loop.conditionBranch);
                 m_builder.SetInsertPoint(loop.conditionBranch);
                 loops.push_back(loop);
@@ -1009,7 +1005,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 }
 
                 // Jump to the condition branch
-                freeHeap();
+                freeScopeHeap();
                 m_builder.CreateBr(loop.conditionBranch);
 
                 // Switch to the branch after the loop
@@ -1032,7 +1028,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_builder.CreateBr(endBranch);
 
     m_builder.SetInsertPoint(endBranch);
-    freeHeap();
+    assert(m_heap.size() == 1);
+    freeScopeHeap();
     syncVariables(targetVariables);
 
     // End and verify the function
@@ -1535,6 +1532,8 @@ void LLVMCodeBuilder::pushScopeLevel()
         m_scopeLists.push_back(listTypes);
     } else
         m_scopeLists.push_back(m_scopeLists.back());
+
+    m_heap.push_back({});
 }
 
 void LLVMCodeBuilder::popScopeLevel()
@@ -1556,6 +1555,9 @@ void LLVMCodeBuilder::popScopeLevel()
     }
 
     m_scopeLists.pop_back();
+
+    freeScopeHeap();
+    m_heap.pop_back();
 }
 
 void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
@@ -1590,13 +1592,28 @@ LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg)
     return reg.get();
 }
 
-void LLVMCodeBuilder::freeHeap()
+void LLVMCodeBuilder::freeLater(llvm::Value *value)
+{
+    assert(!m_heap.empty());
+
+    if (m_heap.empty())
+        return;
+
+    m_heap.back().push_back(value);
+}
+
+void LLVMCodeBuilder::freeScopeHeap()
 {
-    // Free dynamically allocated memory
-    for (llvm::Value *ptr : m_heap)
+    if (m_heap.empty())
+        return;
+
+    // Free dynamically allocated memory in current scope
+    auto &heap = m_heap.back();
+
+    for (llvm::Value *ptr : heap)
         m_builder.CreateFree(ptr);
 
-    m_heap.clear();
+    heap.clear();
 }
 
 llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType targetType)
@@ -1668,7 +1685,7 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType
                 case Compiler::StaticType::Unknown: {
                     // Cast to string
                     llvm::Value *ptr = m_builder.CreateCall(resolve_value_toCString(), reg->value);
-                    m_heap.push_back(ptr); // deallocate later
+                    freeLater(ptr);
                     return ptr;
                 }
 
@@ -1731,7 +1748,7 @@ llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegister *reg, Compiler::StaticTy
                 case Compiler::StaticType::Number: {
                     // Convert double to string
                     llvm::Value *ptr = m_builder.CreateCall(resolve_value_doubleToCString(), reg->value);
-                    m_heap.push_back(ptr); // deallocate later
+                    freeLater(ptr);
                     return ptr;
                 }
 
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 2d34f01d..91f42e80 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -118,7 +118,8 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         LLVMRegister *addReg(std::shared_ptr<LLVMRegister> reg);
 
-        void freeHeap();
+        void freeLater(llvm::Value *value);
+        void freeScopeHeap();
         llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType);
         llvm::Value *castRawValue(LLVMRegister *reg, Compiler::StaticType targetType);
         llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType);
@@ -202,7 +203,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         bool m_defaultWarp = false;
         bool m_warp = false;
 
-        std::vector<llvm::Value *> m_heap;
+        std::vector<std::vector<llvm::Value *>> m_heap; // scopes
 
         std::shared_ptr<ExecutableCode> m_output;
 };
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index a4afc9b1..9a7f2f1c 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2765,6 +2765,7 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     m_builder->endIf();
 
     // Nested 1
+    CompilerValue *str = m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { m_builder->addConstValue("test") });
     v = m_builder->addConstValue(true);
     m_builder->beginIfStatement(v);
     {
@@ -2779,6 +2780,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
             v = m_builder->addConstValue(1);
             m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
+            // str should still be allocated
+            m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str });
+
             v = m_builder->addConstValue(false);
             m_builder->beginIfStatement(v);
             m_builder->beginElseBranch();
@@ -2787,6 +2791,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
                 m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
             }
             m_builder->endIf();
+
+            // str should still be allocated
+            m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str });
         }
         m_builder->endIf();
     }
@@ -2807,6 +2814,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     }
     m_builder->endIf();
 
+    // str should still be allocated
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str });
+
     // Nested 2
     v = m_builder->addConstValue(false);
     m_builder->beginIfStatement(v);
@@ -2826,6 +2836,8 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
     }
     m_builder->beginElseBranch();
     {
+        str = m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { m_builder->addConstValue("test") });
+
         v = m_builder->addConstValue(true);
         m_builder->beginIfStatement(v);
         {
@@ -2834,6 +2846,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
         }
         m_builder->beginElseBranch();
         m_builder->endIf();
+
+        // str should still be allocated
+        m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str });
     }
     m_builder->endIf();
 
@@ -2855,8 +2870,12 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
         "no_args_ret\n"
         "1_arg 9\n"
         "1_arg 1\n"
+        "test\n"
         "1_arg 2\n"
-        "1_arg 7\n";
+        "test\n"
+        "test\n"
+        "1_arg 7\n"
+        "test\n";
 
     EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false));
     testing::internal::CaptureStdout();
@@ -3144,6 +3163,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     m_builder->endLoop();
 
     // Nested
+    CompilerValue *str = m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { m_builder->addConstValue("test") });
     v = m_builder->addConstValue(2);
     m_builder->beginRepeatLoop(v);
     {
@@ -3152,6 +3172,9 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
         {
             v = m_builder->addConstValue(1);
             m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+            // str should still be allocated
+            m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str });
         }
         m_builder->endLoop();
 
@@ -3168,6 +3191,9 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
     }
     m_builder->endLoop();
 
+    // str should still be allocated
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str });
+
     auto code = m_builder->finalize();
     Script script(&m_target, nullptr, nullptr);
     script.setCode(code);
@@ -3186,17 +3212,22 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop)
         "0\n"
         "1\n"
         "1_arg 1\n"
+        "test\n"
         "1_arg 1\n"
+        "test\n"
         "1_arg 2\n"
         "0\n"
         "1\n"
         "2\n"
         "1_arg 1\n"
+        "test\n"
         "1_arg 1\n"
+        "test\n"
         "1_arg 2\n"
         "0\n"
         "1\n"
-        "2\n";
+        "2\n"
+        "test\n";
 
     EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false));
     testing::internal::CaptureStdout();

From 33f519c5588f33aa166659c44a3a7a91bbfc37fe Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 14:03:08 +0100
Subject: [PATCH 189/259] ScriptBuilder: Refactor reporter blocks

---
 include/scratchcpp/dev/test/scriptbuilder.h |  2 +-
 src/dev/test/scriptbuilder.cpp              | 29 +++++++++++++++------
 test/dev/test_api/scriptbuilder_test.cpp    | 28 +++++++++++++++++---
 3 files changed, 47 insertions(+), 12 deletions(-)

diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h
index 65a3383c..e2045a2e 100644
--- a/include/scratchcpp/dev/test/scriptbuilder.h
+++ b/include/scratchcpp/dev/test/scriptbuilder.h
@@ -31,7 +31,6 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
         ~ScriptBuilder();
 
         void addBlock(const std::string &opcode);
-        void addReporterBlock(const std::string &opcode);
         void captureBlockReturnValue();
 
         void addValueInput(const std::string &name, const Value &value);
@@ -47,6 +46,7 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
         void addEntityField(const std::string &name, std::shared_ptr<Entity> entity);
 
         std::shared_ptr<Block> currentBlock();
+        std::shared_ptr<Block> takeBlock();
 
         void build();
         void run();
diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index 51ec57e7..641b3d0e 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -59,19 +59,13 @@ void ScriptBuilder::addBlock(const std::string &opcode)
     addBlock(impl->lastBlock);
 }
 
-/*! Creates a reporter block with the given opcode to be used with captureBlockReturnValue() later. */
-void ScriptBuilder::addReporterBlock(const std::string &opcode)
-{
-    impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), opcode);
-}
-
 /*! Captures the return value of the created reporter block. It can be retrieved using capturedValues() later. */
 void ScriptBuilder::captureBlockReturnValue()
 {
     if (!impl->lastBlock)
         return;
 
-    auto valueBlock = impl->lastBlock;
+    auto valueBlock = takeBlock();
     addBlock("script_builder_capture");
     addObscuredInput("VALUE", valueBlock);
 }
@@ -104,6 +98,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Bl
         return;
 
     auto block = valueBlock;
+    block->setParent(impl->lastBlock);
 
     while (block) {
         block->setId(std::to_string(impl->blockId++));
@@ -112,7 +107,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Bl
         auto parent = block->parent();
         auto next = block->next();
 
-        if (parent)
+        if (parent && block != valueBlock)
             parent->setNext(block);
 
         if (next)
@@ -228,6 +223,24 @@ std::shared_ptr<Block> ScriptBuilder::currentBlock()
     return impl->lastBlock;
 }
 
+/*! Removes the current block from the script and returns it. Can be used in inputs later. */
+std::shared_ptr<Block> ScriptBuilder::takeBlock()
+{
+    if (!impl->lastBlock)
+        return nullptr;
+
+    auto block = impl->lastBlock;
+    impl->blocks.pop_back();
+
+    if (!impl->blocks.empty())
+        impl->blocks.back()->setNext(nullptr);
+
+    block->setParent(nullptr);
+    block->setNext(nullptr);
+
+    return block;
+}
+
 /*! Builds and compiles the script. */
 void ScriptBuilder::build()
 {
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index 058caaf6..2239832a 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -129,6 +129,28 @@ TEST_F(ScriptBuilderTest, AddObscuredInputMultipleBlocks)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\ntest\ntest\n");
 }
 
+TEST_F(ScriptBuilderTest, AdvancedObscuredInput)
+{
+    for (int i = 1; i <= 3; i++) {
+        m_builder->addBlock("test_input");
+        m_builder->addValueInput("INPUT", i);
+        auto valueBlock = m_builder->takeBlock();
+
+        m_builder->addBlock("test_input");
+        m_builder->addObscuredInput("INPUT", valueBlock);
+        valueBlock = m_builder->takeBlock();
+
+        m_builder->addBlock("test_print");
+        m_builder->addObscuredInput("STRING", valueBlock);
+    }
+
+    m_builder->build();
+
+    testing::internal::CaptureStdout();
+    m_builder->run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "1\n2\n3\n");
+}
+
 TEST_F(ScriptBuilderTest, AddNullObscuredInput)
 {
     m_builder->addBlock("test_print");
@@ -221,15 +243,15 @@ TEST_F(ScriptBuilderTest, AddEntityField)
     m_builder->build();
 }
 
-TEST_F(ScriptBuilderTest, ReporterBlocks)
+TEST_F(ScriptBuilderTest, CaptureBlockReturnValue)
 {
-    m_builder->addReporterBlock("test_teststr");
+    m_builder->addBlock("test_teststr");
     auto block = m_builder->currentBlock();
     ASSERT_TRUE(block);
     ASSERT_EQ(block->opcode(), "test_teststr");
     m_builder->captureBlockReturnValue();
 
-    m_builder->addReporterBlock("test_input");
+    m_builder->addBlock("test_input");
     m_builder->addValueInput("INPUT", -93.4);
     block = m_builder->currentBlock();
     ASSERT_TRUE(block);

From 39aba5de28aaf116d08ab21ba9cfe235f4135360 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 14:03:48 +0100
Subject: [PATCH 190/259] Implement data_itemoflist

---
 src/dev/blocks/listblocks.cpp        | 20 ++++++++
 src/dev/blocks/listblocks.h          |  1 +
 test/dev/blocks/CMakeLists.txt       |  1 +
 test/dev/blocks/list_blocks_test.cpp | 69 ++++++++++++++++++++++++++++
 test/dev/blocks/util.cpp             | 12 +++++
 5 files changed, 103 insertions(+)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index f00a9b41..93be1f53 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -27,6 +27,7 @@ void ListBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "data_deletealloflist", &compileDeleteAllOfList);
     engine->addCompileFunction(this, "data_insertatlist", &compileInsertAtList);
     engine->addCompileFunction(this, "data_replaceitemoflist", &compileReplaceItemOfList);
+    engine->addCompileFunction(this, "data_itemoflist", &compileItemOfList);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -150,3 +151,22 @@ CompilerValue *ListBlocks::compileReplaceItemOfList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::compileItemOfList(Compiler *compiler)
+{
+    List *list = static_cast<List *>(compiler->field("LIST")->valuePtr().get());
+    assert(list);
+
+    if (list) {
+        CompilerValue *index = compiler->addInput("INDEX");
+        CompilerValue *min = compiler->addConstValue(-1);
+        CompilerValue *max = compiler->addListSize(list);
+        index = getListIndex(compiler, index, list, max);
+        index = compiler->createSub(index, compiler->addConstValue(1));
+        CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
+        CompilerValue *item = compiler->addListItem(list, index);
+        return compiler->createSelect(cond, item, compiler->addConstValue(Value()), Compiler::StaticType::Unknown);
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index 4f4e4aa4..f682d36b 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -24,6 +24,7 @@ class ListBlocks : public IExtension
         static CompilerValue *compileDeleteAllOfList(Compiler *compiler);
         static CompilerValue *compileInsertAtList(Compiler *compiler);
         static CompilerValue *compileReplaceItemOfList(Compiler *compiler);
+        static CompilerValue *compileItemOfList(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/CMakeLists.txt b/test/dev/blocks/CMakeLists.txt
index c36e43cf..5239c047 100644
--- a/test/dev/blocks/CMakeLists.txt
+++ b/test/dev/blocks/CMakeLists.txt
@@ -169,6 +169,7 @@ if (LIBSCRATCHCPP_ENABLE_LIST_BLOCKS)
       GTest::gmock_main
       scratchcpp
       scratchcpp_mocks
+      block_test_deps
     )
 
     gtest_discover_tests(list_blocks_test)
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index 69c12229..abe1c1c4 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -5,12 +5,16 @@
 #include <scratchcpp/dev/test/scriptbuilder.h>
 #include <scratchcpp/script.h>
 #include <scratchcpp/thread.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
 #include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/dev/executablecode.h>
 #include <enginemock.h>
 #include <randomgeneratormock.h>
 
 #include "../common.h"
+#include "util.h"
 #include "dev/blocks/listblocks.h"
 
 using namespace libscratchcpp;
@@ -26,6 +30,7 @@ class ListBlocksTest : public testing::Test
             m_extension = std::make_unique<ListBlocks>();
             m_engine = m_project.engine().get();
             m_extension->registerBlocks(m_engine);
+            registerBlocks(m_engine, m_extension.get());
         }
 
         std::unique_ptr<IExtension> m_extension;
@@ -267,3 +272,67 @@ TEST_F(ListBlocksTest, ReplaceItemOfList)
     ASSERT_EQ(list1->toString(), "Lorem ipsum dolor sit -53.18");
     ASSERT_EQ(list2->toString(), "dolor world false ipsum abc lorem");
 }
+
+TEST_F(ListBlocksTest, ItemOfList)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list = std::make_shared<List>("list", "");
+    list->append("Lorem");
+    list->append("ipsum");
+    list->append("dolor");
+    list->append(123);
+    list->append(true);
+    target->addList(list);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addTest = [&builder](const Value &index, std::shared_ptr<List> list) {
+        builder.addBlock("test_const_string");
+        builder.addValueInput("STRING", index);
+        auto valueBlock = builder.takeBlock();
+
+        builder.addBlock("data_itemoflist");
+        builder.addObscuredInput("INDEX", valueBlock);
+        builder.addEntityField("LIST", list);
+        auto block = builder.takeBlock();
+
+        builder.addBlock("test_print");
+        builder.addObscuredInput("STRING", block);
+        return builder.currentBlock();
+    };
+
+    auto block = addTest(3, list);
+    addTest(5, list);
+    addTest(0, list);
+    addTest(6, list);
+
+    addTest("last", list);
+    addTest("random", list);
+    addTest("any", list);
+
+    builder.build();
+
+    Compiler compiler(&m_engineMock, target.get());
+    auto code = compiler.compile(block);
+    Script script(target.get(), block, &m_engineMock);
+    script.setCode(code);
+    Thread thread(target.get(), &m_engineMock, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    ctx->setRng(&m_rng);
+
+    static const std::string expected =
+        "dolor\n"
+        "true\n"
+        "0\n"
+        "0\n"
+        "true\n"
+        "123\n"
+        "Lorem\n";
+
+    EXPECT_CALL(m_rng, randint(1, 5)).WillOnce(Return(4)).WillOnce(Return(1));
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+    ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true");
+}
diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp
index 78345800..c81d9c7c 100644
--- a/test/dev/blocks/util.cpp
+++ b/test/dev/blocks/util.cpp
@@ -35,6 +35,11 @@ void registerBlocks(IEngine *engine, IExtension *extension)
 
     engine->addCompileFunction(extension, "test_input", [](Compiler *compiler) -> CompilerValue * { return compiler->addInput("INPUT"); });
 
+    engine->addCompileFunction(extension, "test_const_string", [](Compiler *compiler) -> CompilerValue * {
+        auto input = compiler->addInput("STRING");
+        return compiler->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { input });
+    });
+
     engine->addCompileFunction(extension, "test_set_var", [](Compiler *compiler) -> CompilerValue * {
         Variable *var = static_cast<Variable *>(compiler->field("VARIABLE")->valuePtr().get());
         compiler->createVariableWrite(var, compiler->addInput("VALUE"));
@@ -52,4 +57,11 @@ extern "C" bool test_condition()
     return conditionReturnValue;
 }
 
+extern "C" char *test_const_string(const char *str)
+{
+    char *ret = (char *)malloc((strlen(str) + 1) * sizeof(char));
+    strcpy(ret, str);
+    return ret;
+}
+
 } // namespace libscratchcpp

From a82f3fa3f2e24051a96c75f10d11869f8b1c2bfd Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 14:11:31 +0100
Subject: [PATCH 191/259] Implement data_itemnumoflist

---
 src/dev/blocks/listblocks.cpp        | 14 +++++++++
 src/dev/blocks/listblocks.h          |  1 +
 test/dev/blocks/list_blocks_test.cpp | 43 ++++++++++++++++++++++++++++
 3 files changed, 58 insertions(+)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index 93be1f53..5ee694e5 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -28,6 +28,7 @@ void ListBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "data_insertatlist", &compileInsertAtList);
     engine->addCompileFunction(this, "data_replaceitemoflist", &compileReplaceItemOfList);
     engine->addCompileFunction(this, "data_itemoflist", &compileItemOfList);
+    engine->addCompileFunction(this, "data_itemnumoflist", &compileItemNumOfList);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -170,3 +171,16 @@ CompilerValue *ListBlocks::compileItemOfList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::compileItemNumOfList(Compiler *compiler)
+{
+    List *list = static_cast<List *>(compiler->field("LIST")->valuePtr().get());
+    assert(list);
+
+    if (list) {
+        CompilerValue *item = compiler->addInput("ITEM");
+        return compiler->createAdd(compiler->addListItemIndex(list, item), compiler->addConstValue(1));
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index f682d36b..a1131b5e 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -25,6 +25,7 @@ class ListBlocks : public IExtension
         static CompilerValue *compileInsertAtList(Compiler *compiler);
         static CompilerValue *compileReplaceItemOfList(Compiler *compiler);
         static CompilerValue *compileItemOfList(Compiler *compiler);
+        static CompilerValue *compileItemNumOfList(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index abe1c1c4..62d4dff9 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -336,3 +336,46 @@ TEST_F(ListBlocksTest, ItemOfList)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
     ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true");
 }
+
+TEST_F(ListBlocksTest, ItemNumOfList)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list = std::make_shared<List>("list", "");
+    list->append("Lorem");
+    list->append("ipsum");
+    list->append("dolor");
+    list->append(123);
+    list->append(true);
+    list->append("dolor");
+    target->addList(list);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addTest = [&builder](const Value &item, std::shared_ptr<List> list) {
+        builder.addBlock("data_itemnumoflist");
+        builder.addValueInput("ITEM", item);
+        builder.addEntityField("LIST", list);
+        auto block = builder.takeBlock();
+
+        builder.addBlock("test_print");
+        builder.addObscuredInput("STRING", block);
+        return builder.currentBlock();
+    };
+
+    auto block = addTest("dolor", list);
+    addTest(true, list);
+    addTest("nonexistent", list);
+
+    builder.build();
+
+    static const std::string expected =
+        "3\n"
+        "5\n"
+        "0\n";
+
+    testing::internal::CaptureStdout();
+    builder.run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+    ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true dolor");
+}

From d08feb667250aca44d88bb59875031bffd55e277 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 14:15:25 +0100
Subject: [PATCH 192/259] Implement data_lengthoflist

---
 src/dev/blocks/listblocks.cpp        | 12 ++++++++
 src/dev/blocks/listblocks.h          |  1 +
 test/dev/blocks/list_blocks_test.cpp | 45 ++++++++++++++++++++++++++++
 3 files changed, 58 insertions(+)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index 5ee694e5..d6701148 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -29,6 +29,7 @@ void ListBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "data_replaceitemoflist", &compileReplaceItemOfList);
     engine->addCompileFunction(this, "data_itemoflist", &compileItemOfList);
     engine->addCompileFunction(this, "data_itemnumoflist", &compileItemNumOfList);
+    engine->addCompileFunction(this, "data_lengthoflist", &compileLengthOfList);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -184,3 +185,14 @@ CompilerValue *ListBlocks::compileItemNumOfList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::compileLengthOfList(Compiler *compiler)
+{
+    List *list = static_cast<List *>(compiler->field("LIST")->valuePtr().get());
+    assert(list);
+
+    if (list)
+        return compiler->addListSize(list);
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index a1131b5e..e195ad48 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -26,6 +26,7 @@ class ListBlocks : public IExtension
         static CompilerValue *compileReplaceItemOfList(Compiler *compiler);
         static CompilerValue *compileItemOfList(Compiler *compiler);
         static CompilerValue *compileItemNumOfList(Compiler *compiler);
+        static CompilerValue *compileLengthOfList(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index 62d4dff9..db0df8c1 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -379,3 +379,48 @@ TEST_F(ListBlocksTest, ItemNumOfList)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
     ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true dolor");
 }
+
+TEST_F(ListBlocksTest, LengthOfList)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list1 = std::make_shared<List>("list1", "");
+    list1->append("Lorem");
+    list1->append("ipsum");
+    list1->append("dolor");
+    list1->append(123);
+    list1->append(true);
+    target->addList(list1);
+
+    auto list2 = std::make_shared<List>("list2", "");
+    list2->append(1);
+    list2->append(false);
+    target->addList(list2);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addTest = [&builder](std::shared_ptr<List> list) {
+        builder.addBlock("data_lengthoflist");
+        builder.addEntityField("LIST", list);
+        auto block = builder.takeBlock();
+
+        builder.addBlock("test_print");
+        builder.addObscuredInput("STRING", block);
+        return builder.currentBlock();
+    };
+
+    auto block = addTest(list1);
+    addTest(list2);
+
+    builder.build();
+
+    static const std::string expected =
+        "5\n"
+        "2\n";
+
+    testing::internal::CaptureStdout();
+    builder.run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+    ASSERT_EQ(list1->toString(), "Lorem ipsum dolor 123 true");
+    ASSERT_EQ(list2->toString(), "1 false");
+}

From 0d91530509dcb00eac4b136cd2b74b1240adb781 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 14:18:32 +0100
Subject: [PATCH 193/259] Implement data_listcontainsitem

---
 src/dev/blocks/listblocks.cpp        | 14 +++++++++
 src/dev/blocks/listblocks.h          |  1 +
 test/dev/blocks/list_blocks_test.cpp | 43 ++++++++++++++++++++++++++++
 3 files changed, 58 insertions(+)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index d6701148..4da342d3 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -30,6 +30,7 @@ void ListBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "data_itemoflist", &compileItemOfList);
     engine->addCompileFunction(this, "data_itemnumoflist", &compileItemNumOfList);
     engine->addCompileFunction(this, "data_lengthoflist", &compileLengthOfList);
+    engine->addCompileFunction(this, "data_listcontainsitem", &compileListContainsItem);
 }
 
 CompilerValue *ListBlocks::compileAddToList(Compiler *compiler)
@@ -196,3 +197,16 @@ CompilerValue *ListBlocks::compileLengthOfList(Compiler *compiler)
 
     return nullptr;
 }
+
+CompilerValue *ListBlocks::compileListContainsItem(Compiler *compiler)
+{
+    List *list = static_cast<List *>(compiler->field("LIST")->valuePtr().get());
+    assert(list);
+
+    if (list) {
+        CompilerValue *item = compiler->addInput("ITEM");
+        return compiler->addListContains(list, item);
+    }
+
+    return nullptr;
+}
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index e195ad48..3a8d0126 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -27,6 +27,7 @@ class ListBlocks : public IExtension
         static CompilerValue *compileItemOfList(Compiler *compiler);
         static CompilerValue *compileItemNumOfList(Compiler *compiler);
         static CompilerValue *compileLengthOfList(Compiler *compiler);
+        static CompilerValue *compileListContainsItem(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index db0df8c1..bccc5bb1 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -424,3 +424,46 @@ TEST_F(ListBlocksTest, LengthOfList)
     ASSERT_EQ(list1->toString(), "Lorem ipsum dolor 123 true");
     ASSERT_EQ(list2->toString(), "1 false");
 }
+
+TEST_F(ListBlocksTest, ListContainsItem)
+{
+    auto target = std::make_shared<Sprite>();
+
+    auto list = std::make_shared<List>("list", "");
+    list->append("Lorem");
+    list->append("ipsum");
+    list->append("dolor");
+    list->append(123);
+    list->append(true);
+    list->append("dolor");
+    target->addList(list);
+
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    auto addTest = [&builder](const Value &item, std::shared_ptr<List> list) {
+        builder.addBlock("data_listcontainsitem");
+        builder.addEntityField("LIST", list);
+        builder.addValueInput("ITEM", item);
+        auto block = builder.takeBlock();
+
+        builder.addBlock("test_print");
+        builder.addObscuredInput("STRING", block);
+        return builder.currentBlock();
+    };
+
+    auto block = addTest("dolor", list);
+    addTest(true, list);
+    addTest("nonexistent", list);
+
+    builder.build();
+
+    static const std::string expected =
+        "true\n"
+        "true\n"
+        "false\n";
+
+    testing::internal::CaptureStdout();
+    builder.run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+    ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true dolor");
+}

From 9a61a365defa6d444e3bbaf1fd38d357d312788e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 15:51:56 +0100
Subject: [PATCH 194/259] Add missing yield and promise to broadcast and wait
 block

---
 src/dev/blocks/eventblocks.cpp        | 5 +++++
 test/dev/blocks/event_blocks_test.cpp | 8 ++++++++
 2 files changed, 13 insertions(+)

diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index 4189bcfa..a2b18b3b 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -8,6 +8,7 @@
 #include <scratchcpp/dev/executioncontext.h>
 #include <scratchcpp/thread.h>
 #include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/dev/promise.h>
 
 #include "eventblocks.h"
 
@@ -104,6 +105,7 @@ CompilerValue *EventBlocks::compileBroadcastAndWait(Compiler *compiler)
     auto input = compiler->addInput("BROADCAST_INPUT");
     auto wait = compiler->addConstValue(true);
     compiler->addFunctionCallWithCtx("event_broadcast", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { input, wait });
+    compiler->createYield();
     return nullptr;
 }
 
@@ -126,4 +128,7 @@ extern "C" void event_broadcast(ExecutionContext *ctx, const char *name, bool wa
 
     for (int index : broadcasts)
         engine->broadcast(index, thread, wait);
+
+    if (wait)
+        ctx->setPromise(std::make_shared<Promise>());
 }
diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp
index 470ccaf0..4c274e6d 100644
--- a/test/dev/blocks/event_blocks_test.cpp
+++ b/test/dev/blocks/event_blocks_test.cpp
@@ -166,6 +166,8 @@ TEST_F(EventBlocksTest, Broadcast)
         EXPECT_CALL(m_engineMock, broadcast(1, &thread, false));
         EXPECT_CALL(m_engineMock, broadcast(4, &thread, false));
         thread.run();
+        ASSERT_TRUE(thread.isFinished());
+        ASSERT_FALSE(thread.promise());
     }
 
     {
@@ -180,6 +182,8 @@ TEST_F(EventBlocksTest, Broadcast)
         EXPECT_CALL(m_engineMock, broadcast(7, &thread, false));
         EXPECT_CALL(m_engineMock, broadcast(8, &thread, false));
         thread.run();
+        ASSERT_TRUE(thread.isFinished());
+        ASSERT_FALSE(thread.promise());
     }
 }
 
@@ -213,6 +217,8 @@ TEST_F(EventBlocksTest, BroadcastAndWait)
         EXPECT_CALL(m_engineMock, broadcast(1, &thread, true));
         EXPECT_CALL(m_engineMock, broadcast(4, &thread, true));
         thread.run();
+        ASSERT_FALSE(thread.isFinished());
+        ASSERT_TRUE(thread.promise());
     }
 
     {
@@ -227,6 +233,8 @@ TEST_F(EventBlocksTest, BroadcastAndWait)
         EXPECT_CALL(m_engineMock, broadcast(7, &thread, true));
         EXPECT_CALL(m_engineMock, broadcast(8, &thread, true));
         thread.run();
+        ASSERT_FALSE(thread.isFinished());
+        ASSERT_TRUE(thread.promise());
     }
 }
 

From e8d2abe082b9e8b10edbacf42462ac4d90dabad8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 29 Dec 2024 15:52:44 +0100
Subject: [PATCH 195/259] Enable more engine tests (LLVM)

---
 test/engine/engine_test.cpp | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index 8944cac0..cb0c17b6 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -126,7 +126,6 @@ TEST(EngineTest, Clear)
     ScratchConfiguration::removeExtension(extension);
 }
 
-#ifndef USE_LLVM
 TEST(EngineTest, ClearThreadAboutToStopSignal)
 {
     Project p("3_threads.sb3");
@@ -199,6 +198,7 @@ TEST(EngineTest, StopSignal)
     }
 }
 
+#ifndef USE_LLVM
 TEST(EngineTest, CompileAndExecuteMonitors)
 {
     Engine engine;
@@ -352,7 +352,6 @@ TEST(EngineTest, GlobalVolume)
     engine.setGlobalVolume(92.36);
 }
 
-#ifndef USE_LLVM
 TEST(EngineTest, Step)
 {
     Project p("step.sb3");
@@ -377,7 +376,6 @@ TEST(EngineTest, Step)
         ASSERT_EQ(test2->value().toInt(), i > 1 ? 3 : 1);
     }
 }
-#endif
 
 TEST(EngineTest, EventLoop)
 {
@@ -628,7 +626,6 @@ TEST(EngineTest, KeyState)
     ASSERT_TRUE(engine.keyPressed("any"));
 }
 
-#ifndef USE_LLVM
 TEST(EngineTest, WhenKeyPressed)
 {
     Project p("when_key_pressed.sb3");
@@ -894,7 +891,6 @@ TEST(EngineTest, MouseWheel)
     ASSERT_VAR(stage, "any");
     ASSERT_EQ(GET_VAR(stage, "any")->value().toInt(), 0);
 }
-#endif
 
 TEST(EngineTest, MouseX)
 {
@@ -1059,7 +1055,6 @@ TEST(EngineTest, Broadcasts)
     ASSERT_EQ(engine.findBroadcastById("c"), 2);
 }
 
-#ifndef USE_LLVM
 TEST(EngineTest, TargetClickScripts)
 {
     Project p("target_click_scripts.sb3");
@@ -1165,7 +1160,6 @@ TEST(EngineTest, TargetClickScripts)
     ASSERT_VAR(stage, "stage");
     ASSERT_EQ(GET_VAR(stage, "stage")->value().toInt(), 2);
 }
-#endif
 
 TEST(EngineTest, Targets)
 {
@@ -1872,6 +1866,7 @@ TEST(EngineTest, BroadcastsProject)
     ASSERT_VAR(stage, "test5");
     ASSERT_EQ(GET_VAR(stage, "test5")->value().toString(), "2 2 0 0");
 }
+#endif
 
 TEST(EngineTest, StopAll)
 {
@@ -1949,6 +1944,7 @@ TEST(EngineTest, StopOtherScriptsInSprite)
     ASSERT_EQ(GET_VAR(stage, "l")->value().toInt(), 110);
 }
 
+#ifndef USE_LLVM
 TEST(EngineTest, EdgeActivatedHats)
 {
     Project p("when_greater_than.sb3");
@@ -2057,7 +2053,6 @@ TEST(EngineTest, NoCrashOnBroadcastSelfCall)
     p.run();
 }
 
-#ifndef USE_LLVM
 TEST(EngineTest, NoRefreshWhenCallingRunningBroadcast)
 {
     // Regtest for #257
@@ -2078,6 +2073,7 @@ TEST(EngineTest, NoRefreshWhenCallingRunningBroadcast)
     ASSERT_TRUE(GET_VAR(stage, "passed2")->value().toBool());
 }
 
+#ifndef USE_LLVM
 TEST(EngineTest, NoStopWhenCallingRunningBroadcastFromCustomBlock)
 {
     // Regtest for #257
@@ -2096,6 +2092,7 @@ TEST(EngineTest, NoStopWhenCallingRunningBroadcastFromCustomBlock)
     ASSERT_VAR(stage, "passed2");
     ASSERT_TRUE(GET_VAR(stage, "passed2")->value().toBool());
 }
+#endif
 
 TEST(EngineTest, ResetRunningHats)
 {
@@ -2217,7 +2214,6 @@ TEST(EngineTest, NoCrashWithUndefinedVariableOrList)
     ASSERT_EQ(list->id(), "}l+w#Er5y!:*gh~5!3t%");
     ASSERT_TRUE(list->empty());
 }
-#endif
 
 TEST(EngineTest, AlwaysStopCloneThreads)
 {
@@ -2289,4 +2285,4 @@ TEST(EngineTest, BroadcastAndWaitCaseInsensitive)
     ASSERT_VAR(stage, "passed");
     ASSERT_TRUE(GET_VAR(stage, "passed")->value().toBool());
 }
-#endif // USE_LLVM
+#endif

From dde8dad1effc2da8add1914c93d7a3a4251392fe Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 30 Dec 2024 14:49:59 +0100
Subject: [PATCH 196/259] Add CompilerContext class

---
 CMakeLists.txt                             |  1 +
 include/scratchcpp/dev/compilercontext.h   | 30 ++++++++++++++++++++++
 src/dev/engine/CMakeLists.txt              |  3 +++
 src/dev/engine/compilercontext.cpp         | 25 ++++++++++++++++++
 src/dev/engine/compilercontext_p.cpp       | 11 ++++++++
 src/dev/engine/compilercontext_p.h         | 19 ++++++++++++++
 test/dev/compiler/CMakeLists.txt           |  1 +
 test/dev/compiler/compilercontext_test.cpp | 14 ++++++++++
 8 files changed, 104 insertions(+)
 create mode 100644 include/scratchcpp/dev/compilercontext.h
 create mode 100644 src/dev/engine/compilercontext.cpp
 create mode 100644 src/dev/engine/compilercontext_p.cpp
 create mode 100644 src/dev/engine/compilercontext_p.h
 create mode 100644 test/dev/compiler/compilercontext_test.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 95f74700..8ca8c1aa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -74,6 +74,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
     target_sources(scratchcpp
       PUBLIC
         include/scratchcpp/dev/compiler.h
+        include/scratchcpp/dev/compilercontext.h
         include/scratchcpp/dev/compilervalue.h
         include/scratchcpp/dev/compilerconstant.h
         include/scratchcpp/dev/compilerlocalvariable.h
diff --git a/include/scratchcpp/dev/compilercontext.h b/include/scratchcpp/dev/compilercontext.h
new file mode 100644
index 00000000..2a4c2420
--- /dev/null
+++ b/include/scratchcpp/dev/compilercontext.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include "../global.h"
+#include "../spimpl.h"
+
+namespace libscratchcpp
+{
+
+class IEngine;
+class Target;
+class CompilerContextPrivate;
+
+/*! \brief The CompilerContext represents a context for a specific target which is used with the Compiler class. */
+class LIBSCRATCHCPP_EXPORT CompilerContext
+{
+    public:
+        CompilerContext(IEngine *engine, Target *target);
+        CompilerContext(const CompilerContext &) = delete;
+        virtual ~CompilerContext() { }
+
+        IEngine *engine() const;
+        Target *target() const;
+
+    private:
+        spimpl::unique_impl_ptr<CompilerContextPrivate> impl;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt
index 9320b664..be71b580 100644
--- a/src/dev/engine/CMakeLists.txt
+++ b/src/dev/engine/CMakeLists.txt
@@ -3,6 +3,9 @@ target_sources(scratchcpp
     compiler.cpp
     compiler_p.cpp
     compiler_p.h
+    compilercontext.cpp
+    compilercontext_p.cpp
+    compilercontext_p.h
     compilervalue.cpp
     compilervalue_p.cpp
     compilervalue_p.h
diff --git a/src/dev/engine/compilercontext.cpp b/src/dev/engine/compilercontext.cpp
new file mode 100644
index 00000000..12bc613e
--- /dev/null
+++ b/src/dev/engine/compilercontext.cpp
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <scratchcpp/dev/compilercontext.h>
+
+#include "compilercontext_p.h"
+
+using namespace libscratchcpp;
+
+/*! Constructs CompilerContext. */
+CompilerContext::CompilerContext(IEngine *engine, Target *target) :
+    impl(spimpl::make_unique_impl<CompilerContextPrivate>(engine, target))
+{
+}
+
+/*! Returns the engine of the project. */
+IEngine *CompilerContext::engine() const
+{
+    return impl->engine;
+}
+
+/*! Returns the target of this context. */
+Target *CompilerContext::target() const
+{
+    return impl->target;
+}
diff --git a/src/dev/engine/compilercontext_p.cpp b/src/dev/engine/compilercontext_p.cpp
new file mode 100644
index 00000000..5f4c42f2
--- /dev/null
+++ b/src/dev/engine/compilercontext_p.cpp
@@ -0,0 +1,11 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "compilercontext_p.h"
+
+using namespace libscratchcpp;
+
+CompilerContextPrivate::CompilerContextPrivate(IEngine *engine, Target *target) :
+    engine(engine),
+    target(target)
+{
+}
diff --git a/src/dev/engine/compilercontext_p.h b/src/dev/engine/compilercontext_p.h
new file mode 100644
index 00000000..5a97a56a
--- /dev/null
+++ b/src/dev/engine/compilercontext_p.h
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace libscratchcpp
+{
+
+class IEngine;
+class Target;
+
+struct CompilerContextPrivate
+{
+        CompilerContextPrivate(IEngine *engine, Target *target);
+
+        IEngine *engine = nullptr;
+        Target *target = nullptr;
+};
+
+} // namespace libscratchcpp
diff --git a/test/dev/compiler/CMakeLists.txt b/test/dev/compiler/CMakeLists.txt
index cdc9696c..639bbbb0 100644
--- a/test/dev/compiler/CMakeLists.txt
+++ b/test/dev/compiler/CMakeLists.txt
@@ -1,6 +1,7 @@
 add_executable(
   compiler_test
   compiler_test.cpp
+  compilercontext_test.cpp
   compilervalue_test.cpp
   compilerconstant_test.cpp
   compilerlocalvariable_test.cpp
diff --git a/test/dev/compiler/compilercontext_test.cpp b/test/dev/compiler/compilercontext_test.cpp
new file mode 100644
index 00000000..0cc611b5
--- /dev/null
+++ b/test/dev/compiler/compilercontext_test.cpp
@@ -0,0 +1,14 @@
+#include <scratchcpp/dev/compilercontext.h>
+#include <enginemock.h>
+#include <targetmock.h>
+
+using namespace libscratchcpp;
+
+TEST(CompilerContextTest, Constructors)
+{
+    EngineMock engine;
+    TargetMock target;
+    CompilerContext ctx(&engine, &target);
+    ASSERT_EQ(ctx.engine(), &engine);
+    ASSERT_EQ(ctx.target(), &target);
+}

From 9567a0418fc348c675c467e7de6a55d35934b3be Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 30 Dec 2024 14:50:43 +0100
Subject: [PATCH 197/259] Refactor compiler to use shared context for each
 target

---
 include/scratchcpp/dev/compiler.h             |   4 +
 src/dev/engine/compiler.cpp                   |  22 +-
 src/dev/engine/compiler_p.cpp                 |  17 +-
 src/dev/engine/compiler_p.h                   |   8 +-
 .../engine/internal/codebuilderfactory.cpp    |  12 +-
 src/dev/engine/internal/codebuilderfactory.h  |   3 +-
 src/dev/engine/internal/icodebuilderfactory.h |   5 +-
 src/dev/engine/internal/llvm/CMakeLists.txt   |   2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 251 +++++++++--------
 .../engine/internal/llvm/llvmcodebuilder.h    |  10 +-
 .../internal/llvm/llvmcompilercontext.cpp     |  65 +++++
 .../internal/llvm/llvmcompilercontext.h       |  53 ++++
 .../engine/internal/llvm/llvmcoroutine.cpp    |   4 +-
 .../internal/llvm/llvmexecutablecode.cpp      |  53 ++--
 .../engine/internal/llvm/llvmexecutablecode.h |  19 +-
 src/engine/internal/engine.cpp                |   8 +-
 src/engine/internal/engine.h                  |   6 +
 test/dev/compiler/compiler_test.cpp           | 262 +++++++++---------
 test/dev/llvm/llvmcodebuilder_test.cpp        | 111 ++++----
 test/dev/llvm/llvmexecutablecode_test.cpp     |  61 ++--
 test/mocks/codebuilderfactorymock.h           |   3 +-
 test/scratch_classes/inputvalue_test.cpp      |   2 +-
 22 files changed, 568 insertions(+), 413 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmcompilercontext.cpp
 create mode 100644 src/dev/engine/internal/llvm/llvmcompilercontext.h

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 4486099e..0ac77bbc 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -11,6 +11,7 @@
 namespace libscratchcpp
 {
 
+class CompilerContext;
 class IEngine;
 class Target;
 class ExecutableCode;
@@ -39,6 +40,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         using ArgTypes = std::vector<StaticType>;
         using Args = std::vector<CompilerValue *>;
 
+        Compiler(CompilerContext *ctx);
         Compiler(IEngine *engine, Target *target);
         Compiler(const Compiler &) = delete;
 
@@ -132,6 +134,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
 
         const std::unordered_set<std::string> &unsupportedBlocks() const;
 
+        static std::shared_ptr<CompilerContext> createContext(IEngine *engine, Target *target);
+
     private:
         CompilerValue *addInput(Input *input);
 
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index bcbc8a35..b8e0ff82 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilercontext.h>
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/input.h>
@@ -12,7 +13,13 @@
 
 using namespace libscratchcpp;
 
-/*! Constructs Compiler. */
+/*! Constructs Compiler using the given context. */
+Compiler::Compiler(CompilerContext *ctx) :
+    impl(spimpl::make_unique_impl<CompilerPrivate>(ctx))
+{
+}
+
+/*! Constructs Compiler using a new context for the given target. */
 Compiler::Compiler(IEngine *engine, Target *target) :
     impl(spimpl::make_unique_impl<CompilerPrivate>(engine, target))
 {
@@ -21,13 +28,13 @@ Compiler::Compiler(IEngine *engine, Target *target) :
 /*! Returns the Engine of the project. */
 IEngine *Compiler::engine() const
 {
-    return impl->engine;
+    return impl->ctx->engine();
 }
 
 /*! Returns the Target of this compiler. */
 Target *Compiler::target() const
 {
-    return impl->target;
+    return impl->ctx->target();
 }
 
 /*! Returns currently compiled block. */
@@ -39,7 +46,7 @@ std::shared_ptr<libscratchcpp::Block> Compiler::block() const
 /*! Compiles the script starting with the given block. */
 std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBlock)
 {
-    impl->builder = impl->builderFactory->create(impl->target, startBlock->id(), false);
+    impl->builder = impl->builderFactory->create(impl->ctx, false);
     impl->substackTree.clear();
     impl->substackHit = false;
     impl->emptySubstack = false;
@@ -570,6 +577,13 @@ const std::unordered_set<std::string> &Compiler::unsupportedBlocks() const
     return impl->unsupportedBlocks;
 }
 
+/*! Creates a compiler context for the given target. */
+std::shared_ptr<CompilerContext> Compiler::createContext(IEngine *engine, Target *target)
+{
+    CompilerPrivate::initBuilderFactory();
+    return CompilerPrivate::builderFactory->createCtx(engine, target);
+}
+
 CompilerValue *Compiler::addInput(Input *input)
 {
     if (!input)
diff --git a/src/dev/engine/compiler_p.cpp b/src/dev/engine/compiler_p.cpp
index cff14fe9..963c0d3e 100644
--- a/src/dev/engine/compiler_p.cpp
+++ b/src/dev/engine/compiler_p.cpp
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/dev/compiler.h>
 #include <scratchcpp/block.h>
 
 #include "compiler_p.h"
@@ -9,9 +10,21 @@
 
 using namespace libscratchcpp;
 
+CompilerPrivate::CompilerPrivate(CompilerContext *ctx) :
+    ctx(ctx)
+{
+    assert(ctx);
+    initBuilderFactory();
+}
+
 CompilerPrivate::CompilerPrivate(IEngine *engine, Target *target) :
-    engine(engine),
-    target(target)
+    ctxPtr(Compiler::createContext(engine, target)),
+    ctx(ctxPtr.get())
+{
+    initBuilderFactory();
+}
+
+void CompilerPrivate::initBuilderFactory()
 {
     if (!builderFactory)
         builderFactory = CodeBuilderFactory::instance().get();
diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h
index add1021c..593accbf 100644
--- a/src/dev/engine/compiler_p.h
+++ b/src/dev/engine/compiler_p.h
@@ -9,6 +9,7 @@
 namespace libscratchcpp
 {
 
+class CompilerContext;
 class IEngine;
 class Target;
 class Block;
@@ -24,12 +25,15 @@ struct CompilerPrivate
             IfStatement
         };
 
+        CompilerPrivate(CompilerContext *ctx);
         CompilerPrivate(IEngine *engine, Target *target);
 
+        static void initBuilderFactory();
+
         void substackEnd();
 
-        IEngine *engine = nullptr;
-        Target *target = nullptr;
+        std::shared_ptr<CompilerContext> ctxPtr; // for self-managed contexts
+        CompilerContext *ctx = nullptr;
 
         std::shared_ptr<Block> block;
         int customIfStatementCount = 0;
diff --git a/src/dev/engine/internal/codebuilderfactory.cpp b/src/dev/engine/internal/codebuilderfactory.cpp
index 999a740a..1623c67b 100644
--- a/src/dev/engine/internal/codebuilderfactory.cpp
+++ b/src/dev/engine/internal/codebuilderfactory.cpp
@@ -2,6 +2,7 @@
 
 #include "codebuilderfactory.h"
 #include "llvm/llvmcodebuilder.h"
+#include "llvm/llvmcompilercontext.h"
 
 using namespace libscratchcpp;
 
@@ -12,7 +13,14 @@ std::shared_ptr<CodeBuilderFactory> CodeBuilderFactory::instance()
     return m_instance;
 }
 
-std::shared_ptr<ICodeBuilder> CodeBuilderFactory::create(Target *target, const std::string &id, bool warp) const
+std::shared_ptr<ICodeBuilder> CodeBuilderFactory::create(CompilerContext *ctx, bool warp) const
 {
-    return std::make_shared<LLVMCodeBuilder>(target, id, warp);
+    assert(dynamic_cast<LLVMCompilerContext *>(ctx));
+    return std::make_shared<LLVMCodeBuilder>(static_cast<LLVMCompilerContext *>(ctx), warp);
+}
+
+std::shared_ptr<CompilerContext> CodeBuilderFactory::createCtx(IEngine *engine, Target *target) const
+{
+    auto ptr = std::make_shared<LLVMCompilerContext>(engine, target);
+    return ptr;
 }
diff --git a/src/dev/engine/internal/codebuilderfactory.h b/src/dev/engine/internal/codebuilderfactory.h
index 9a498717..e8eea9c9 100644
--- a/src/dev/engine/internal/codebuilderfactory.h
+++ b/src/dev/engine/internal/codebuilderfactory.h
@@ -11,7 +11,8 @@ class CodeBuilderFactory : public ICodeBuilderFactory
 {
     public:
         static std::shared_ptr<CodeBuilderFactory> instance();
-        std::shared_ptr<ICodeBuilder> create(Target *target, const std::string &id, bool warp) const override;
+        std::shared_ptr<ICodeBuilder> create(CompilerContext *ctx, bool warp) const override;
+        std::shared_ptr<CompilerContext> createCtx(IEngine *engine, Target *target) const override;
 
     private:
         static std::shared_ptr<CodeBuilderFactory> m_instance;
diff --git a/src/dev/engine/internal/icodebuilderfactory.h b/src/dev/engine/internal/icodebuilderfactory.h
index 07265463..75b485c2 100644
--- a/src/dev/engine/internal/icodebuilderfactory.h
+++ b/src/dev/engine/internal/icodebuilderfactory.h
@@ -8,14 +8,17 @@ namespace libscratchcpp
 {
 
 class ICodeBuilder;
+class CompilerContext;
 class Target;
+class IEngine;
 
 class ICodeBuilderFactory
 {
     public:
         virtual ~ICodeBuilderFactory() { }
 
-        virtual std::shared_ptr<ICodeBuilder> create(Target *target, const std::string &id, bool warp) const = 0;
+        virtual std::shared_ptr<ICodeBuilder> create(CompilerContext *ctx, bool warp) const = 0;
+        virtual std::shared_ptr<CompilerContext> createCtx(IEngine *engine, Target *target) const = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index 315be55a..e1bb3ef5 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -16,6 +16,8 @@ target_sources(scratchcpp
     llvmtypes.cpp
     llvmtypes.h
     llvmfunctions.cpp
+    llvmcompilercontext.cpp
+    llvmcompilercontext.h
     llvmexecutablecode.cpp
     llvmexecutablecode.h
     llvmexecutioncontext.cpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 98d4190b..a4e2eec4 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1,6 +1,5 @@
 // SPDX-License-Identifier: Apache-2.0
 
-#include <llvm/Support/TargetSelect.h>
 #include <llvm/IR/Verifier.h>
 #include <llvm/ExecutionEngine/Orc/LLJIT.h>
 #include <llvm/Passes/PassBuilder.h>
@@ -13,6 +12,7 @@
 #include <scratchcpp/dev/compilerlocalvariable.h>
 
 #include "llvmcodebuilder.h"
+#include "llvmcompilercontext.h"
 #include "llvmexecutablecode.h"
 #include "llvmconstantregister.h"
 #include "llvmifstatement.h"
@@ -24,18 +24,15 @@ using namespace libscratchcpp;
 static std::unordered_map<ValueType, Compiler::StaticType>
     TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } };
 
-LLVMCodeBuilder::LLVMCodeBuilder(Target *target, const std::string &id, bool warp) :
-    m_target(target),
-    m_id(id),
-    m_module(std::make_unique<llvm::Module>(id, m_ctx)),
-    m_builder(m_ctx),
+LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, bool warp) :
+    m_ctx(ctx),
+    m_target(ctx->target()),
+    m_llvmCtx(*ctx->llvmCtx()),
+    m_module(ctx->module()),
+    m_builder(m_llvmCtx),
     m_defaultWarp(warp),
     m_warp(warp)
 {
-    llvm::InitializeNativeTarget();
-    llvm::InitializeNativeTargetAsmPrinter();
-    llvm::InitializeNativeTargetAsmParser();
-
     initTypes();
     createVariableMap();
     createListMap();
@@ -61,23 +58,23 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
     // Create function
     // void *f(ExecutionContext *, Target *, ValueData **, List **)
-    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType, pointerType }, false);
-    llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
+    llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module);
     llvm::Value *executionContextPtr = func->getArg(0);
     llvm::Value *targetPtr = func->getArg(1);
     llvm::Value *targetVariables = func->getArg(2);
     llvm::Value *targetLists = func->getArg(3);
 
-    llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
-    llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_ctx, "end", func);
+    llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", func);
+    llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", func);
     m_builder.SetInsertPoint(entry);
 
     // Init coroutine
     std::unique_ptr<LLVMCoroutine> coro;
 
     if (!m_warp)
-        coro = std::make_unique<LLVMCoroutine>(m_module.get(), &m_builder, func);
+        coro = std::make_unique<LLVMCoroutine>(m_module, &m_builder, func);
 
     std::vector<LLVMIfStatement> ifStatements;
     std::vector<LLVMLoop> loops;
@@ -123,13 +120,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Add execution context arg
                 if (step.functionCtxArg) {
-                    types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0));
+                    types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0));
                     args.push_back(executionContextPtr);
                 }
 
                 // Add target pointer arg
                 if (step.functionTargetArg) {
-                    types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0));
+                    types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0));
                     args.push_back(targetPtr);
                 }
 
@@ -291,7 +288,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg1 = step.args[0];
                 const auto &arg2 = step.args[1];
                 // rem(a, b) / b < 0.0 ? rem(a, b) + b : rem(a, b)
-                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
+                llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
                 llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first));
                 llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first));
                 llvm::Value *value = m_builder.CreateFRem(num1, num2);                                // rem(a, b)
@@ -304,15 +301,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // x >= 0.0 ? round(x) : (x >= -0.5 ? -0.0 : floor(x + 0.5))
-                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
-                llvm::Constant *negativeZero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(-0.0));
-                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
-                llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::floor, m_builder.getDoubleTy());
+                llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Constant *negativeZero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-0.0));
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::floor, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
-                llvm::Value *notNegative = m_builder.CreateFCmpOGE(num, zero);                                                                             // num >= 0.0
-                llvm::Value *roundNum = m_builder.CreateCall(roundFunc, num);                                                                              // round(num)
-                llvm::Value *negativeCond = m_builder.CreateFCmpOGE(num, llvm::ConstantFP::get(m_ctx, llvm::APFloat(-0.5)));                               // num >= -0.5
-                llvm::Value *negativeRound = m_builder.CreateCall(floorFunc, m_builder.CreateFAdd(num, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.5)))); // floor(x + 0.5)
+                llvm::Value *notNegative = m_builder.CreateFCmpOGE(num, zero);                                                                                 // num >= 0.0
+                llvm::Value *roundNum = m_builder.CreateCall(roundFunc, num);                                                                                  // round(num)
+                llvm::Value *negativeCond = m_builder.CreateFCmpOGE(num, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-0.5)));                               // num >= -0.5
+                llvm::Value *negativeRound = m_builder.CreateCall(floorFunc, m_builder.CreateFAdd(num, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.5)))); // floor(x + 0.5)
                 step.functionReturnReg->value = m_builder.CreateSelect(notNegative, roundNum, m_builder.CreateSelect(negativeCond, negativeZero, negativeRound));
                 break;
             }
@@ -320,7 +317,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::Abs: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
-                llvm::Function *absFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::fabs, m_builder.getDoubleTy());
+                llvm::Function *absFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::fabs, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateCall(absFunc, num);
                 break;
@@ -329,7 +326,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::Floor: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
-                llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::floor, m_builder.getDoubleTy());
+                llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::floor, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateCall(floorFunc, num);
                 break;
@@ -338,7 +335,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::Ceil: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
-                llvm::Function *ceilFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::ceil, m_builder.getDoubleTy());
+                llvm::Function *ceilFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::ceil, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateCall(ceilFunc, num);
                 break;
@@ -349,8 +346,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 // sqrt(x) + 0.0
                 // This avoids negative zero
-                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
-                llvm::Function *sqrtFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::sqrt, m_builder.getDoubleTy());
+                llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Function *sqrtFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::sqrt, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateCall(sqrtFunc, num), zero);
                 break;
@@ -361,12 +358,12 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 // round(sin(x * pi / 180.0) * 1e10) / 1e10 + 0.0
                 // +0.0 to avoid -0.0
-                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
-                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
-                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
-                llvm::Constant *factor = llvm::ConstantFP::get(m_ctx, llvm::APFloat(1e10));
-                llvm::Function *sinFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::sin, m_builder.getDoubleTy());
-                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0));
+                llvm::Constant *factor = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(1e10));
+                llvm::Function *sinFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::sin, m_builder.getDoubleTy());
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 llvm::Value *sinResult = m_builder.CreateCall(sinFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // sin(x * pi / 180)
                 llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(sinResult, factor));                    // round(sin(x * 180) * 1e10)
@@ -378,11 +375,11 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // round(cos(x * pi / 180.0) * 1e10) / 1e10
-                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
-                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
-                llvm::Constant *factor = llvm::ConstantFP::get(m_ctx, llvm::APFloat(1e10));
-                llvm::Function *cosFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::cos, m_builder.getDoubleTy());
-                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0));
+                llvm::Constant *factor = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(1e10));
+                llvm::Function *cosFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::cos, m_builder.getDoubleTy());
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 llvm::Value *cosResult = m_builder.CreateCall(cosFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // cos(x * pi / 180)
                 llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(cosResult, factor));                    // round(cos(x * 180) * 1e10)
@@ -395,19 +392,19 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 // ((mod = rem(x, 360.0)) == -270.0 || mod == 90.0) ? inf : ((mod == -90.0 || mod == 270.0) ? -inf : round(tan(x * pi / 180.0) * 1e10) / 1e10 + 0.0)
                 // +0.0 to avoid -0.0
-                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
-                llvm::Constant *full = llvm::ConstantFP::get(m_ctx, llvm::APFloat(360.0));
+                llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Constant *full = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(360.0));
                 llvm::Constant *posInf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false);
                 llvm::Constant *negInf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), true);
-                llvm::Constant *undefined1 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(-270.0));
-                llvm::Constant *undefined2 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(90.0));
-                llvm::Constant *undefined3 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(-90.0));
-                llvm::Constant *undefined4 = llvm::ConstantFP::get(m_ctx, llvm::APFloat(270.0));
-                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
-                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
-                llvm::Constant *factor = llvm::ConstantFP::get(m_ctx, llvm::APFloat(1e10));
-                llvm::Function *tanFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::tan, m_builder.getDoubleTy());
-                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, m_builder.getDoubleTy());
+                llvm::Constant *undefined1 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-270.0));
+                llvm::Constant *undefined2 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(90.0));
+                llvm::Constant *undefined3 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-90.0));
+                llvm::Constant *undefined4 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(270.0));
+                llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0));
+                llvm::Constant *factor = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(1e10));
+                llvm::Function *tanFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::tan, m_builder.getDoubleTy());
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 llvm::Value *mod = m_builder.CreateFRem(num, full);
                 llvm::Value *isUndefined1 = m_builder.CreateFCmpOEQ(mod, undefined1);                                               // rem(x, 360.0) == -270.0
@@ -427,10 +424,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 // asin(x) * 180.0 / pi + 0.0
                 // +0.0 to avoid -0.0
-                llvm::Constant *zero = llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0));
-                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
-                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
-                llvm::Function *asinFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::asin, m_builder.getDoubleTy());
+                llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0));
+                llvm::Function *asinFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::asin, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(asinFunc, num), piDeg), pi), zero);
                 break;
@@ -440,9 +437,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // acos(x) * 180.0 / pi
-                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
-                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
-                llvm::Function *acosFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::acos, m_builder.getDoubleTy());
+                llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0));
+                llvm::Function *acosFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::acos, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(acosFunc, num), piDeg), pi);
                 break;
@@ -452,9 +449,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // atan(x) * 180.0 / pi
-                llvm::Constant *pi = llvm::ConstantFP::get(m_ctx, llvm::APFloat(std::acos(-1.0)));
-                llvm::Constant *piDeg = llvm::ConstantFP::get(m_ctx, llvm::APFloat(180.0));
-                llvm::Function *atanFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::atan, m_builder.getDoubleTy());
+                llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0)));
+                llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0));
+                llvm::Function *atanFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::atan, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(atanFunc, num), piDeg), pi);
                 break;
@@ -464,7 +461,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // log(x)
-                llvm::Function *logFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::log, m_builder.getDoubleTy());
+                llvm::Function *logFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::log, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateCall(logFunc, num);
                 break;
@@ -474,7 +471,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // log10(x)
-                llvm::Function *log10Func = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::log10, m_builder.getDoubleTy());
+                llvm::Function *log10Func = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::log10, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateCall(log10Func, num);
                 break;
@@ -484,7 +481,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // exp(x)
-                llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::exp, m_builder.getDoubleTy());
+                llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::exp, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateCall(expFunc, num);
                 break;
@@ -494,7 +491,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 // exp10(x)
-                llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::exp10, m_builder.getDoubleTy());
+                llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::exp10, m_builder.getDoubleTy());
                 llvm::Value *num = removeNaN(castValue(arg.second, arg.first));
                 step.functionReturnReg->value = m_builder.CreateCall(expFunc, num);
                 break;
@@ -658,9 +655,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
                 llvm::Value *isAllocated = m_builder.CreateICmpUGT(allocatedSize, size);
-                llvm::BasicBlock *ifBlock = llvm::BasicBlock::Create(m_ctx, "", func);
-                llvm::BasicBlock *elseBlock = llvm::BasicBlock::Create(m_ctx, "", func);
-                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *ifBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *elseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
                 m_builder.CreateCondBr(isAllocated, ifBlock, elseBlock);
 
                 // If there's enough space, use the allocated memory
@@ -804,7 +801,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::BeginIf: {
                 LLVMIfStatement statement;
                 statement.beforeIf = m_builder.GetInsertBlock();
-                statement.body = llvm::BasicBlock::Create(m_ctx, "", func);
+                statement.body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 // Use last reg
                 assert(step.args.size() == 1);
@@ -835,13 +832,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Jump to the branch after the if statement
                 assert(!statement.afterIf);
-                statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func);
+                statement.afterIf = llvm::BasicBlock::Create(m_llvmCtx, "", func);
                 freeScopeHeap();
                 m_builder.CreateBr(statement.afterIf);
 
                 // Create else branch
                 assert(!statement.elseBranch);
-                statement.elseBranch = llvm::BasicBlock::Create(m_ctx, "", func);
+                statement.elseBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 // Since there's an else branch, the conditional instruction should jump to it
                 m_builder.SetInsertPoint(statement.beforeIf);
@@ -859,7 +856,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Jump to the branch after the if statement
                 if (!statement.afterIf)
-                    statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func);
+                    statement.afterIf = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 m_builder.CreateBr(statement.afterIf);
 
@@ -888,9 +885,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.CreateStore(zero, loop.index);
 
                 // Create branches
-                llvm::BasicBlock *roundBranch = llvm::BasicBlock::Create(m_ctx, "", func);
-                loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func);
-                loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *roundBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 // Use last reg for count
                 assert(step.args.size() == 1);
@@ -900,12 +897,12 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Value *isInf = m_builder.CreateFCmpOEQ(count, llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false));
 
                 // Clamp count if <= 0 (we can skip the loop if count is not positive)
-                llvm::Value *comparison = m_builder.CreateFCmpULE(count, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)));
+                llvm::Value *comparison = m_builder.CreateFCmpULE(count, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)));
                 m_builder.CreateCondBr(comparison, loop.afterLoop, roundBranch);
 
                 // Round (Scratch-specific behavior)
                 m_builder.SetInsertPoint(roundBranch);
-                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, { count->getType() });
+                llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, { count->getType() });
                 count = m_builder.CreateCall(roundFunc, { count });
                 count = m_builder.CreateFPToUI(count, m_builder.getInt64Ty()); // cast to unsigned integer
                 count = m_builder.CreateSelect(isInf, zero, count);
@@ -916,10 +913,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // Check index
                 m_builder.SetInsertPoint(loop.conditionBranch);
 
-                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 if (!loop.afterLoop)
-                    loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func);
+                    loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index);
                 comparison = m_builder.CreateOr(isInf, m_builder.CreateICmpULT(currentIndex, count));
@@ -946,8 +943,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMLoop &loop = loops.back();
 
                 // Create branches
-                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func);
-                loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 // Use last reg
                 assert(step.args.size() == 1);
@@ -967,8 +964,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMLoop &loop = loops.back();
 
                 // Create branches
-                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", func);
-                loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
                 // Use last reg
                 assert(step.args.size() == 1);
@@ -986,7 +983,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::BeginLoopCondition: {
                 LLVMLoop loop;
                 loop.isRepeatLoop = false;
-                loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func);
+                loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
                 m_builder.CreateBr(loop.conditionBranch);
                 m_builder.SetInsertPoint(loop.conditionBranch);
                 loops.push_back(loop);
@@ -1018,7 +1015,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
             case LLVMInstruction::Type::Stop: {
                 m_builder.CreateBr(endBranch);
-                llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_ctx, "", func);
+                llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
                 m_builder.SetInsertPoint(nextBranch);
                 break;
             }
@@ -1043,17 +1040,17 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     // Create resume function
     // bool resume(void *)
     funcType = llvm::FunctionType::get(m_builder.getInt1Ty(), pointerType, false);
-    func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "resume", m_module.get());
+    llvm::Function *resumeFunc = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "resume", m_module);
 
-    entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
+    entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", resumeFunc);
     m_builder.SetInsertPoint(entry);
 
     if (m_warp)
         m_builder.CreateRet(m_builder.getInt1(true));
     else
-        m_builder.CreateRet(coro->createResume(func->getArg(0)));
+        m_builder.CreateRet(coro->createResume(resumeFunc->getArg(0)));
 
-    verifyFunction(func);
+    verifyFunction(resumeFunc);
 
 #ifdef PRINT_LLVM_IR
     std::cout << std::endl << "=== LLVM IR (" << m_module->getName().str() << ") ===" << std::endl;
@@ -1064,7 +1061,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     // Optimize
     optimize();
 
-    return std::make_shared<LLVMExecutableCode>(std::move(m_module));
+    return std::make_shared<LLVMExecutableCode>(m_ctx, func->getName().str(), resumeFunc->getName().str());
 }
 
 CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
@@ -1564,7 +1561,7 @@ void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
 {
     if (llvm::verifyFunction(*func, &llvm::errs())) {
         llvm::errs() << "error: LLVM function verficiation failed!\n";
-        llvm::errs() << "script hat ID: " << m_id << "\n";
+        llvm::errs() << "module name: " << m_module->getName() << "\n";
     }
 }
 
@@ -1659,7 +1656,7 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType
                     // True if != 0
                     llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0);
                     llvm::Value *numberValue = m_builder.CreateLoad(m_builder.getDoubleTy(), ptr);
-                    return m_builder.CreateFCmpONE(numberValue, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)));
+                    return m_builder.CreateFCmpONE(numberValue, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)));
                 }
 
                 case Compiler::StaticType::Bool: {
@@ -1692,7 +1689,7 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType
                 case Compiler::StaticType::String: {
                     // Read string pointer directly
                     llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0);
-                    return m_builder.CreateLoad(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), ptr);
+                    return m_builder.CreateLoad(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), ptr);
                 }
 
                 default:
@@ -1732,7 +1729,7 @@ llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegister *reg, Compiler::StaticTy
             switch (reg->type()) {
                 case Compiler::StaticType::Number:
                     // Cast double to bool (true if != 0)
-                    return m_builder.CreateFCmpONE(reg->value, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)));
+                    return m_builder.CreateFCmpONE(reg->value, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)));
 
                 case Compiler::StaticType::String:
                     // Convert string to bool
@@ -1775,7 +1772,7 @@ llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::St
     switch (targetType) {
         case Compiler::StaticType::Number: {
             const double nan = std::numeric_limits<double>::quiet_NaN();
-            return llvm::ConstantFP::get(m_ctx, llvm::APFloat(value.isNaN() ? nan : value.toDouble()));
+            return llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(value.isNaN() ? nan : value.toDouble()));
         }
 
         case Compiler::StaticType::Bool:
@@ -1814,7 +1811,7 @@ llvm::Type *LLVMCodeBuilder::getType(Compiler::StaticType type)
             return m_builder.getInt1Ty();
 
         case Compiler::StaticType::String:
-            return llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+            return llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
 
         default:
             assert(false);
@@ -1830,7 +1827,7 @@ llvm::Value *LLVMCodeBuilder::isNaN(llvm::Value *num)
 llvm::Value *LLVMCodeBuilder::removeNaN(llvm::Value *num)
 {
     // Replace NaN with zero
-    return m_builder.CreateSelect(isNaN(num), llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)), num);
+    return m_builder.CreateSelect(isNaN(num), llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)), num);
 }
 
 llvm::Value *LLVMCodeBuilder::getVariablePtr(llvm::Value *targetVariables, Variable *variable)
@@ -1854,7 +1851,7 @@ llvm::Value *LLVMCodeBuilder::getListPtr(llvm::Value *targetLists, List *list)
         // If this is a local sprite list, use the list array at runtime (for clones)
         assert(m_targetListMap.find(list) != m_targetListMap.cend());
         const size_t index = m_targetListMap[list];
-        auto pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+        auto pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
         llvm::Value *ptr = m_builder.CreateGEP(pointerType, targetLists, m_builder.getInt64(index));
         return m_builder.CreateLoad(pointerType, ptr);
     }
@@ -2067,12 +2064,12 @@ llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Valu
 llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item, llvm::Function *func)
 {
     llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
-    llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_ctx, "", func);
-    llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(m_ctx, "", func);
-    llvm::BasicBlock *cmpIfBlock = llvm::BasicBlock::Create(m_ctx, "", func);
-    llvm::BasicBlock *cmpElseBlock = llvm::BasicBlock::Create(m_ctx, "", func);
-    llvm::BasicBlock *notFoundBlock = llvm::BasicBlock::Create(m_ctx, "", func);
-    llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_ctx, "", func);
+    llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+    llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+    llvm::BasicBlock *cmpIfBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+    llvm::BasicBlock *cmpElseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+    llvm::BasicBlock *notFoundBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+    llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
 
     // index = 0
     llvm::Value *index = m_builder.CreateAlloca(m_builder.getInt64Ty());
@@ -2105,7 +2102,7 @@ llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMR
     // index = -1
     // goto nextBlock
     m_builder.SetInsertPoint(notFoundBlock);
-    m_builder.CreateStore(llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_ctx), -1, true), index);
+    m_builder.CreateStore(llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_llvmCtx), -1, true), index);
     m_builder.CreateBr(nextBlock);
 
     // nextBlock:
@@ -2381,7 +2378,9 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_bool()
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_cstring()
 {
-    return resolveFunction("value_assign_cstring", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0) }, false));
+    return resolveFunction(
+        "value_assign_cstring",
+        llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0) }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_special()
@@ -2406,27 +2405,27 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toBool()
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toCString()
 {
-    return resolveFunction("value_toCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_valueDataType->getPointerTo(), false));
+    return resolveFunction("value_toCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_valueDataType->getPointerTo(), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_doubleToCString()
 {
-    return resolveFunction("value_doubleToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_builder.getDoubleTy(), false));
+    return resolveFunction("value_doubleToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_builder.getDoubleTy(), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_boolToCString()
 {
-    return resolveFunction("value_boolToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_builder.getInt1Ty(), false));
+    return resolveFunction("value_boolToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_builder.getInt1Ty(), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToDouble()
 {
-    return resolveFunction("value_stringToDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), false));
+    return resolveFunction("value_stringToDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToBool()
 {
-    return resolveFunction("value_stringToBool", llvm::FunctionType::get(m_builder.getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), false));
+    return resolveFunction("value_stringToBool", llvm::FunctionType::get(m_builder.getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_equals()
@@ -2449,79 +2448,79 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower()
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_clear()
 {
-    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_clear", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_remove()
 {
-    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_remove", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr, m_builder.getInt64Ty() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_append_empty()
 {
-    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_append_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_insert_empty()
 {
-    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_insert_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_data()
 {
-    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_data", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size_ptr()
 {
-    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_alloc_size_ptr()
 {
-    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_alloc_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string()
 {
-    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("list_to_string", llvm::FunctionType::get(pointerType, { pointerType }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random()
 {
-    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     llvm::Type *valuePtr = m_valueDataType->getPointerTo();
     return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, valuePtr, valuePtr }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double()
 {
-    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_long()
 {
-    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("llvm_random_long", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt64Ty(), m_builder.getInt64Ty() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool()
 {
-    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
-    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     return resolveFunction("strcasecmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false));
 }
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 91f42e80..f504050a 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -16,13 +16,13 @@
 namespace libscratchcpp
 {
 
-class Target;
+class LLVMCompilerContext;
 class LLVMConstantRegister;
 
 class LLVMCodeBuilder : public ICodeBuilder
 {
     public:
-        LLVMCodeBuilder(Target *target, const std::string &id, bool warp);
+        LLVMCodeBuilder(LLVMCompilerContext *ctx, bool warp);
 
         std::shared_ptr<ExecutableCode> finalize() override;
 
@@ -190,9 +190,9 @@ class LLVMCodeBuilder : public ICodeBuilder
         std::unordered_map<List *, LLVMListPtr> m_listPtrs;
         std::vector<std::unordered_map<LLVMListPtr *, Compiler::StaticType>> m_scopeLists;
 
-        std::string m_id;
-        llvm::LLVMContext m_ctx;
-        std::unique_ptr<llvm::Module> m_module;
+        LLVMCompilerContext *m_ctx;
+        llvm::LLVMContext &m_llvmCtx;
+        llvm::Module *m_module = nullptr;
         llvm::IRBuilder<> m_builder;
 
         llvm::StructType *m_valueDataType = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
new file mode 100644
index 00000000..0341bc79
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <llvm/Support/TargetSelect.h>
+
+#include <scratchcpp/target.h>
+#include <iostream>
+
+#include "llvmcompilercontext.h"
+
+using namespace libscratchcpp;
+
+LLVMCompilerContext::LLVMCompilerContext(IEngine *engine, Target *target) :
+    CompilerContext(engine, target),
+    m_llvmCtx(std::make_unique<llvm::LLVMContext>()),
+    m_module(std::make_unique<llvm::Module>(target ? target->name() : "", *m_llvmCtx)),
+    m_llvmCtxPtr(m_llvmCtx.get()),
+    m_modulePtr(m_module.get()),
+    m_jit((initTarget(), llvm::orc::LLJITBuilder().create()))
+{
+    if (!m_jit) {
+        llvm::errs() << "error: failed to create JIT: " << toString(m_jit.takeError()) << "\n";
+        return;
+    }
+}
+
+llvm::LLVMContext *LLVMCompilerContext::llvmCtx()
+{
+    return m_llvmCtxPtr;
+}
+
+llvm::Module *LLVMCompilerContext::module()
+{
+    return m_modulePtr;
+}
+
+void LLVMCompilerContext::initJit()
+{
+    if (m_jitInitialized) {
+        std::cout << "warning: JIT compiler is already initialized" << std::endl;
+        return;
+    }
+
+    m_jitInitialized = true;
+    assert(m_llvmCtx);
+    assert(m_module);
+    std::string name = m_module->getName().str();
+    auto err = m_jit->get()->addIRModule(llvm::orc::ThreadSafeModule(std::move(m_module), std::move(m_llvmCtx)));
+
+    if (err) {
+        llvm::errs() << "error: failed to add module '" << name << "' to JIT: " << toString(std::move(err)) << "\n";
+        return;
+    }
+}
+
+bool LLVMCompilerContext::jitInitialized() const
+{
+    return m_jitInitialized;
+}
+
+void LLVMCompilerContext::initTarget()
+{
+    llvm::InitializeNativeTarget();
+    llvm::InitializeNativeTargetAsmPrinter();
+    llvm::InitializeNativeTargetAsmParser();
+}
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.h b/src/dev/engine/internal/llvm/llvmcompilercontext.h
new file mode 100644
index 00000000..f549ff46
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <llvm/IR/Module.h>
+#include <llvm/ExecutionEngine/Orc/LLJIT.h>
+
+#include <scratchcpp/dev/compilercontext.h>
+
+namespace libscratchcpp
+{
+
+class ExecutionContext;
+class ValueData;
+class List;
+class LLVMExecutableCode;
+
+class LLVMCompilerContext : public CompilerContext
+{
+    public:
+        LLVMCompilerContext(IEngine *engine, Target *target);
+
+        llvm::LLVMContext *llvmCtx();
+        llvm::Module *module();
+
+        void initJit();
+        bool jitInitialized() const;
+
+        template<typename T>
+        T lookupFunction(const std::string &name)
+        {
+            auto func = m_jit->get()->lookup(name);
+
+            if (func)
+                return (T)func->getValue();
+            else {
+                llvm::errs() << "error: failed to lookup LLVM function: " << toString(func.takeError()) << "\n";
+                return nullptr;
+            }
+        }
+
+    private:
+        void initTarget();
+
+        std::unique_ptr<llvm::LLVMContext> m_llvmCtx;
+        std::unique_ptr<llvm::Module> m_module;
+        llvm::LLVMContext *m_llvmCtxPtr = nullptr;
+        llvm::Module *m_modulePtr = nullptr;
+        llvm::Expected<std::unique_ptr<llvm::orc::LLJIT>> m_jit;
+        bool m_jitInitialized = false;
+};
+
+} // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmcoroutine.cpp b/src/dev/engine/internal/llvm/llvmcoroutine.cpp
index 6a4ad6e1..17b834d9 100644
--- a/src/dev/engine/internal/llvm/llvmcoroutine.cpp
+++ b/src/dev/engine/internal/llvm/llvmcoroutine.cpp
@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <llvm/IR/Module.h>
+
 #include "llvmcoroutine.h"
 
 using namespace libscratchcpp;
@@ -28,7 +30,7 @@ LLVMCoroutine::LLVMCoroutine(llvm::Module *module, llvm::IRBuilder<> *builder, l
 
     // Allocate memory
     llvm::Value *coroSizeRet = builder->CreateCall(coroSize, std::nullopt, "size");
-    llvm::Function *mallocFunc = llvm::Function::Create(llvm::FunctionType::get(pointerType, { builder->getInt64Ty() }, false), llvm::Function::ExternalLinkage, "malloc", module);
+    llvm::FunctionCallee mallocFunc = module->getOrInsertFunction("malloc", llvm::FunctionType::get(pointerType, { builder->getInt64Ty() }, false));
     llvm::Value *alloc = builder->CreateCall(mallocFunc, coroSizeRet, "mem");
 
     // Begin
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
index 20aa9dfc..ed88ac60 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp
@@ -6,43 +6,33 @@
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/dev/promise.h>
 #include <scratchcpp/thread.h>
-#include <llvm/Support/Error.h>
 #include <iostream>
 
+#include <llvm/Support/Error.h>
+
 #include "llvmexecutablecode.h"
+#include "llvmcompilercontext.h"
 #include "llvmexecutioncontext.h"
 
 using namespace libscratchcpp;
 
-LLVMExecutableCode::LLVMExecutableCode(std::unique_ptr<llvm::Module> module) :
-    m_ctx(std::make_unique<llvm::LLVMContext>()),
-    m_jit(llvm::orc::LLJITBuilder().create())
+LLVMExecutableCode::LLVMExecutableCode(LLVMCompilerContext *ctx, const std::string &mainFunctionName, const std::string &resumeFunctionName) :
+    m_ctx(ctx),
+    m_mainFunctionName(mainFunctionName),
+    m_resumeFunctionName(resumeFunctionName)
 {
-    if (!m_jit) {
-        llvm::errs() << "error: failed to create JIT: " << toString(m_jit.takeError()) << "\n";
-        return;
-    }
+    assert(m_ctx);
 
-    if (!module)
-        return;
-
-    std::string name = module->getName().str();
-    auto err = m_jit->get()->addIRModule(llvm::orc::ThreadSafeModule(std::move(module), std::move(m_ctx)));
-
-    if (err) {
-        llvm::errs() << "error: failed to add module '" << name << "' to JIT: " << toString(std::move(err)) << "\n";
-        return;
+    if (m_ctx->jitInitialized()) {
+        std::cerr << "error: cannot create LLVM code after JIT compiler had been initialized" << std::endl;
+        assert(false);
     }
-
-    // Lookup functions
-    m_mainFunction = (MainFunctionType)lookupFunction("f");
-    assert(m_mainFunction);
-    m_resumeFunction = (ResumeFunctionType)lookupFunction("resume");
-    assert(m_resumeFunction);
 }
 
 void LLVMExecutableCode::run(ExecutionContext *context)
 {
+    assert(m_mainFunction);
+    assert(m_resumeFunction);
     LLVMExecutionContext *ctx = getContext(context);
 
     if (ctx->finished())
@@ -98,19 +88,12 @@ bool LLVMExecutableCode::isFinished(ExecutionContext *context) const
 
 std::shared_ptr<ExecutionContext> LLVMExecutableCode::createExecutionContext(Thread *thread) const
 {
-    return std::make_shared<LLVMExecutionContext>(thread);
-}
-
-uint64_t LLVMExecutableCode::lookupFunction(const std::string &name)
-{
-    auto func = m_jit->get()->lookup(name);
+    if (!m_ctx->jitInitialized())
+        m_ctx->initJit();
 
-    if (func)
-        return func->getValue();
-    else {
-        llvm::errs() << "error: failed to lookup LLVM function: " << toString(func.takeError()) << "\n";
-        return 0;
-    }
+    m_mainFunction = m_ctx->lookupFunction<MainFunctionType>(m_mainFunctionName);
+    m_resumeFunction = m_ctx->lookupFunction<ResumeFunctionType>(m_resumeFunctionName);
+    return std::make_shared<LLVMExecutionContext>(thread);
 }
 
 LLVMExecutionContext *LLVMExecutableCode::getContext(ExecutionContext *context)
diff --git a/src/dev/engine/internal/llvm/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h
index 6d293b3d..aaa9c9c1 100644
--- a/src/dev/engine/internal/llvm/llvmexecutablecode.h
+++ b/src/dev/engine/internal/llvm/llvmexecutablecode.h
@@ -5,19 +5,18 @@
 #include <scratchcpp/dev/executablecode.h>
 #include <scratchcpp/valuedata.h>
 #include <llvm/IR/LLVMContext.h>
-#include <llvm/ExecutionEngine/Orc/LLJIT.h>
+
+#include "llvmcompilercontext.h"
 
 namespace libscratchcpp
 {
 
-class Target;
-class List;
 class LLVMExecutionContext;
 
 class LLVMExecutableCode : public ExecutableCode
 {
     public:
-        LLVMExecutableCode(std::unique_ptr<llvm::Module> module);
+        LLVMExecutableCode(LLVMCompilerContext *ctx, const std::string &mainFunctionName, const std::string &resumeFunctionName);
 
         void run(ExecutionContext *context) override;
         void kill(libscratchcpp::ExecutionContext *context) override;
@@ -28,18 +27,16 @@ class LLVMExecutableCode : public ExecutableCode
         std::shared_ptr<ExecutionContext> createExecutionContext(Thread *thread) const override;
 
     private:
-        uint64_t lookupFunction(const std::string &name);
-
         using MainFunctionType = void *(*)(ExecutionContext *, Target *, ValueData **, List **);
         using ResumeFunctionType = bool (*)(void *);
 
         static LLVMExecutionContext *getContext(ExecutionContext *context);
 
-        std::unique_ptr<llvm::LLVMContext> m_ctx;
-        llvm::Expected<std::unique_ptr<llvm::orc::LLJIT>> m_jit;
-
-        MainFunctionType m_mainFunction;
-        ResumeFunctionType m_resumeFunction;
+        LLVMCompilerContext *m_ctx = nullptr;
+        std::string m_mainFunctionName;
+        std::string m_resumeFunctionName;
+        mutable MainFunctionType m_mainFunction = nullptr;
+        mutable ResumeFunctionType m_resumeFunction = nullptr;
 };
 
 } // namespace libscratchcpp
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 1be628aa..ee9708a8 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -271,10 +271,14 @@ void Engine::compile()
     // Compile scripts to bytecode
     for (auto target : m_targets) {
         std::cout << "Compiling scripts in target " << target->name() << "..." << std::endl;
-#ifndef USE_LLVM
+#ifdef USE_LLVM
+        auto ctx = Compiler::createContext(this, target.get());
+        m_compilerContexts[target.get()] = ctx;
+        Compiler compiler(ctx.get());
+#else
         std::unordered_map<std::string, unsigned int *> procedureBytecodeMap;
-#endif
         Compiler compiler(this, target.get());
+#endif
         const auto &blocks = target->blocks();
         for (auto block : blocks) {
             if (block->topLevel() && !block->isTopLevelReporter() && !block->shadow()) {
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index 69d64835..ed4b562d 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -19,6 +19,9 @@ class Entity;
 class IClock;
 class IAudioEngine;
 class Thread;
+#ifdef USE_LLVM
+class CompilerContext;
+#endif
 
 class Engine : public IEngine
 {
@@ -238,6 +241,9 @@ class Engine : public IEngine
         static const std::unordered_map<HatType, bool> m_hatEdgeActivated;          // used to check whether a hat is edge-activated (runs when a predicate becomes true)
 
         std::vector<std::shared_ptr<Target>> m_targets;
+#ifdef USE_LLVM
+        std::unordered_map<Target *, std::shared_ptr<CompilerContext>> m_compilerContexts;
+#endif
         std::vector<std::shared_ptr<Broadcast>> m_broadcasts;
         std::unordered_map<Broadcast *, std::vector<Script *>> m_broadcastMap;
         std::unordered_map<Broadcast *, std::vector<Script *>> m_backdropBroadcastMap;
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 945e2d0d..22241e08 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -1,4 +1,5 @@
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilercontext.h>
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/dev/compilerlocalvariable.h>
 #include <scratchcpp/block.h>
@@ -26,7 +27,12 @@ class CompilerTest : public testing::Test
     public:
         void SetUp() override
         {
+            m_ctx = std::make_shared<CompilerContext>(&m_engine, &m_target);
             CompilerPrivate::builderFactory = &m_builderFactory;
+
+            EXPECT_CALL(m_builderFactory, createCtx(&m_engine, &m_target)).WillOnce(Return(m_ctx));
+            m_compiler = std::make_unique<Compiler>(&m_engine, &m_target);
+
             m_builder = std::make_shared<CodeBuilderMock>();
             m_code = std::make_shared<ExecutableCodeMock>();
         }
@@ -39,18 +45,20 @@ class CompilerTest : public testing::Test
             m_testVar.reset();
         }
 
-        void compile(Compiler &compiler, std::shared_ptr<Block> block)
+        void compile(Compiler *compiler, std::shared_ptr<Block> block)
         {
-            ASSERT_EQ(compiler.block(), nullptr);
+            ASSERT_EQ(compiler->block(), nullptr);
             // TODO: Test warp
-            EXPECT_CALL(m_builderFactory, create(compiler.target(), block->id(), false)).WillOnce(Return(m_builder));
+            EXPECT_CALL(m_builderFactory, create(m_ctx.get(), false)).WillOnce(Return(m_builder));
             EXPECT_CALL(*m_builder, finalize()).WillOnce(Return(m_code));
-            ASSERT_EQ(compiler.compile(block), m_code);
-            ASSERT_EQ(compiler.block(), nullptr);
+            ASSERT_EQ(compiler->compile(block), m_code);
+            ASSERT_EQ(compiler->block(), nullptr);
         }
 
         EngineMock m_engine;
         TargetMock m_target;
+        std::unique_ptr<Compiler> m_compiler;
+        std::shared_ptr<CompilerContext> m_ctx;
         CodeBuilderFactoryMock m_builderFactory;
         static inline std::shared_ptr<CodeBuilderMock> m_builder;
         std::shared_ptr<ExecutableCodeMock> m_code;
@@ -58,16 +66,19 @@ class CompilerTest : public testing::Test
         static inline std::shared_ptr<Variable> m_testVar;
 };
 
-TEST_F(CompilerTest, Constructors)
+TEST_F(CompilerTest, Engine)
+{
+    ASSERT_EQ(m_compiler->engine(), &m_engine);
+}
+
+TEST_F(CompilerTest, Target)
 {
-    Compiler compiler(&m_engine, &m_target);
-    ASSERT_EQ(compiler.engine(), &m_engine);
-    ASSERT_EQ(compiler.target(), &m_target);
+    ASSERT_EQ(m_compiler->target(), &m_target);
 }
 
 TEST_F(CompilerTest, AddFunctionCall)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     m_compareBlock = block;
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -87,12 +98,12 @@ TEST_F(CompilerTest, AddFunctionCall)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddTargetFunctionCall)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     m_compareBlock = block;
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -112,12 +123,12 @@ TEST_F(CompilerTest, AddTargetFunctionCall)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddFunctionCallWithCtx)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     m_compareBlock = block;
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -137,12 +148,12 @@ TEST_F(CompilerTest, AddFunctionCallWithCtx)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddConstValue)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerConstant ret(Compiler::StaticType::Unknown, Value());
@@ -159,12 +170,12 @@ TEST_F(CompilerTest, AddConstValue)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddLoopIndex)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Unknown);
@@ -178,12 +189,12 @@ TEST_F(CompilerTest, AddLoopIndex)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddLocalVariableValue)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Number);
@@ -201,12 +212,12 @@ TEST_F(CompilerTest, AddLocalVariableValue)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddVariableValue)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Unknown);
@@ -221,12 +232,12 @@ TEST_F(CompilerTest, AddVariableValue)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddListContents)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Unknown);
@@ -241,12 +252,12 @@ TEST_F(CompilerTest, AddListContents)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddListItem)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Unknown);
@@ -262,12 +273,12 @@ TEST_F(CompilerTest, AddListItem)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddListItemIndex)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Unknown);
@@ -283,12 +294,12 @@ TEST_F(CompilerTest, AddListItemIndex)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddListSize)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Unknown);
@@ -303,12 +314,12 @@ TEST_F(CompilerTest, AddListSize)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddListContains)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         CompilerValue ret(Compiler::StaticType::Unknown);
@@ -324,12 +335,12 @@ TEST_F(CompilerTest, AddListContains)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, AddInput)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     auto valueBlock = std::make_shared<Block>("b", "");
     m_compareBlock = valueBlock;
@@ -415,12 +426,12 @@ TEST_F(CompilerTest, AddInput)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateAdd)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -433,12 +444,12 @@ TEST_F(CompilerTest, CreateAdd)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateSub)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -451,12 +462,12 @@ TEST_F(CompilerTest, CreateSub)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateMul)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -469,12 +480,12 @@ TEST_F(CompilerTest, CreateMul)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateDiv)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -487,12 +498,12 @@ TEST_F(CompilerTest, CreateDiv)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateRandom)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -505,12 +516,12 @@ TEST_F(CompilerTest, CreateRandom)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateRandomInt)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -523,12 +534,12 @@ TEST_F(CompilerTest, CreateRandomInt)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateCmpEQ)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -541,12 +552,12 @@ TEST_F(CompilerTest, CreateCmpEQ)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateCmpGT)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -559,12 +570,12 @@ TEST_F(CompilerTest, CreateCmpGT)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateCmpLT)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -577,12 +588,12 @@ TEST_F(CompilerTest, CreateCmpLT)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateAnd)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -595,12 +606,12 @@ TEST_F(CompilerTest, CreateAnd)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateOr)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -613,12 +624,12 @@ TEST_F(CompilerTest, CreateOr)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateNot)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -630,12 +641,12 @@ TEST_F(CompilerTest, CreateNot)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateMod)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -648,12 +659,12 @@ TEST_F(CompilerTest, CreateMod)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateRound)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -665,12 +676,12 @@ TEST_F(CompilerTest, CreateRound)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateAbs)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -682,12 +693,12 @@ TEST_F(CompilerTest, CreateAbs)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateFloor)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -699,12 +710,12 @@ TEST_F(CompilerTest, CreateFloor)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateCeil)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -716,12 +727,12 @@ TEST_F(CompilerTest, CreateCeil)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateSqrt)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -733,12 +744,12 @@ TEST_F(CompilerTest, CreateSqrt)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateSin)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -750,12 +761,12 @@ TEST_F(CompilerTest, CreateSin)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateCos)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -767,12 +778,12 @@ TEST_F(CompilerTest, CreateCos)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateTan)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -784,12 +795,12 @@ TEST_F(CompilerTest, CreateTan)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateAsin)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -801,12 +812,12 @@ TEST_F(CompilerTest, CreateAsin)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateAcos)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -818,12 +829,12 @@ TEST_F(CompilerTest, CreateAcos)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateAtan)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -835,12 +846,12 @@ TEST_F(CompilerTest, CreateAtan)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateLn)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -852,12 +863,12 @@ TEST_F(CompilerTest, CreateLn)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateLog10)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -869,12 +880,12 @@ TEST_F(CompilerTest, CreateLog10)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateExp)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -886,12 +897,12 @@ TEST_F(CompilerTest, CreateExp)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateExp10)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -903,12 +914,12 @@ TEST_F(CompilerTest, CreateExp10)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateSelect)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -926,12 +937,12 @@ TEST_F(CompilerTest, CreateSelect)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateLocalVariable)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -948,12 +959,12 @@ TEST_F(CompilerTest, CreateLocalVariable)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateLocalVariableWrite)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -966,12 +977,12 @@ TEST_F(CompilerTest, CreateLocalVariableWrite)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateVariableWrite)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -983,12 +994,12 @@ TEST_F(CompilerTest, CreateVariableWrite)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CustomIfStatement)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1008,12 +1019,12 @@ TEST_F(CompilerTest, CustomIfStatement)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CustomWhileLoop)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1026,12 +1037,12 @@ TEST_F(CompilerTest, CustomWhileLoop)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CustomRepeatUntilLoop)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1044,12 +1055,12 @@ TEST_F(CompilerTest, CustomRepeatUntilLoop)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, BeginLoopCondition)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
         EXPECT_CALL(*m_builder, beginLoopCondition());
@@ -1057,12 +1068,12 @@ TEST_F(CompilerTest, BeginLoopCondition)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, MoveToIf)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     EXPECT_CALL(*m_builder, beginElseBranch).Times(0);
 
     auto if1 = std::make_shared<Block>("", "if");
@@ -1094,12 +1105,11 @@ TEST_F(CompilerTest, MoveToIf)
     input->setValueBlock(substack1);
     if2->addInput(input);
 
-    compile(compiler, if1);
+    compile(m_compiler.get(), if1);
 }
 
 TEST_F(CompilerTest, MoveToIfElse)
 {
-    Compiler compiler(&m_engine, &m_target);
 
     auto if1 = std::make_shared<Block>("", "if");
     if1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1264,12 +1274,11 @@ TEST_F(CompilerTest, MoveToIfElse)
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
-    compile(compiler, if1);
+    compile(m_compiler.get(), if1);
 }
 
 TEST_F(CompilerTest, MoveToRepeatLoop)
 {
-    Compiler compiler(&m_engine, &m_target);
 
     auto l1 = std::make_shared<Block>("", "loop");
     l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1354,12 +1363,11 @@ TEST_F(CompilerTest, MoveToRepeatLoop)
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
-    compile(compiler, l1);
+    compile(m_compiler.get(), l1);
 }
 
 TEST_F(CompilerTest, MoveToWhileLoop)
 {
-    Compiler compiler(&m_engine, &m_target);
 
     auto l1 = std::make_shared<Block>("", "loop");
     l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1444,12 +1452,11 @@ TEST_F(CompilerTest, MoveToWhileLoop)
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
-    compile(compiler, l1);
+    compile(m_compiler.get(), l1);
 }
 
 TEST_F(CompilerTest, MoveToRepeatUntilLoop)
 {
-    Compiler compiler(&m_engine, &m_target);
 
     auto l1 = std::make_shared<Block>("", "loop");
     l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1534,12 +1541,12 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop)
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); });
 
     EXPECT_CALL(*m_builder, addConstValue(Value("after")));
-    compile(compiler, l1);
+    compile(m_compiler.get(), l1);
 }
 
 TEST_F(CompilerTest, CreateYield)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1548,12 +1555,12 @@ TEST_F(CompilerTest, CreateYield)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, CreateStop)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("", "");
 
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1562,12 +1569,12 @@ TEST_F(CompilerTest, CreateStop)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, Input)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->addInput(std::make_shared<Input>("TEST", Input::Type::Shadow));
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1576,12 +1583,12 @@ TEST_F(CompilerTest, Input)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, Field)
 {
-    Compiler compiler(&m_engine, &m_target);
+
     auto block = std::make_shared<Block>("a", "");
     block->addField(std::make_shared<Field>("TEST", "test"));
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
@@ -1590,7 +1597,7 @@ TEST_F(CompilerTest, Field)
         return nullptr;
     });
 
-    compile(compiler, block);
+    compile(m_compiler.get(), block);
 }
 
 TEST_F(CompilerTest, UnsupportedBlocks)
@@ -1648,9 +1655,8 @@ TEST_F(CompilerTest, UnsupportedBlocks)
     block4->setParent(block3);
     block3->setNext(block4);
 
-    Compiler compiler(&m_engine, &m_target);
     EXPECT_CALL(*m_builder, addConstValue).WillRepeatedly(Return(nullptr));
-    compile(compiler, block1);
+    compile(m_compiler.get(), block1);
 
-    ASSERT_EQ(compiler.unsupportedBlocks(), std::unordered_set<std::string>({ "block1", "block2", "value_block1", "value_block3", "value_block5", "block4" }));
+    ASSERT_EQ(m_compiler->unsupportedBlocks(), std::unordered_set<std::string>({ "block1", "block2", "value_block1", "value_block3", "value_block5", "block4" }));
 }
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 9a7f2f1c..2cc1141d 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -8,6 +8,7 @@
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
 #include <dev/engine/internal/llvm/llvmcodebuilder.h>
+#include <dev/engine/internal/llvm/llvmcompilercontext.h>
 #include <gmock/gmock.h>
 #include <targetmock.h>
 #include <enginemock.h>
@@ -60,7 +61,12 @@ class LLVMCodeBuilderTest : public testing::Test
             test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency
         }
 
-        void createBuilder(Target *target, bool warp) { m_builder = std::make_unique<LLVMCodeBuilder>(target, "test", warp); }
+        void createBuilder(Target *target, bool warp)
+        {
+            m_ctx = std::make_unique<LLVMCompilerContext>(&m_engine, target);
+            m_builder = std::make_unique<LLVMCodeBuilder>(m_ctx.get(), warp);
+        }
+
         void createBuilder(bool warp) { createBuilder(nullptr, warp); }
 
         CompilerValue *callConstFuncForType(ValueType type, CompilerValue *arg)
@@ -353,7 +359,9 @@ class LLVMCodeBuilderTest : public testing::Test
             ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes << v.toString() << quotes;
         };
 
+        std::unique_ptr<LLVMCompilerContext> m_ctx;
         std::unique_ptr<LLVMCodeBuilder> m_builder;
+        EngineMock m_engine;
         TargetMock m_target; // NOTE: isStage() is used for call expectations
         RandomGeneratorMock m_rng;
 };
@@ -1521,11 +1529,10 @@ TEST_F(LLVMCodeBuilderTest, Exp10)
 
 TEST_F(LLVMCodeBuilderTest, LocalVariables)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     createBuilder(&sprite, true);
 
@@ -1578,11 +1585,10 @@ TEST_F(LLVMCodeBuilderTest, LocalVariables)
 
 TEST_F(LLVMCodeBuilderTest, WriteVariable)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalVar1 = std::make_shared<Variable>("", "");
     auto globalVar2 = std::make_shared<Variable>("", "");
@@ -1642,11 +1648,10 @@ TEST_F(LLVMCodeBuilderTest, WriteVariable)
 
 TEST_F(LLVMCodeBuilderTest, Select)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     createBuilder(&sprite, true);
 
@@ -1730,11 +1735,10 @@ TEST_F(LLVMCodeBuilderTest, Select)
 
 TEST_F(LLVMCodeBuilderTest, ReadVariable)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalVar1 = std::make_shared<Variable>("", "", 87);
     auto globalVar2 = std::make_shared<Variable>("", "", 6.4);
@@ -1791,11 +1795,10 @@ TEST_F(LLVMCodeBuilderTest, ReadVariable)
 
 TEST_F(LLVMCodeBuilderTest, ClearList)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     std::unordered_map<List *, std::string> strings;
 
@@ -1869,11 +1872,10 @@ TEST_F(LLVMCodeBuilderTest, ClearList)
 
 TEST_F(LLVMCodeBuilderTest, RemoveFromList)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     std::unordered_map<List *, std::string> strings;
 
@@ -1915,11 +1917,10 @@ TEST_F(LLVMCodeBuilderTest, RemoveFromList)
 
 TEST_F(LLVMCodeBuilderTest, AppendToList)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     std::unordered_map<List *, std::string> strings;
 
@@ -1975,11 +1976,10 @@ TEST_F(LLVMCodeBuilderTest, AppendToList)
 
 TEST_F(LLVMCodeBuilderTest, InsertToList)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     std::unordered_map<List *, std::string> strings;
 
@@ -2041,11 +2041,10 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
 
 TEST_F(LLVMCodeBuilderTest, ListReplace)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     std::unordered_map<List *, std::string> strings;
 
@@ -2101,11 +2100,10 @@ TEST_F(LLVMCodeBuilderTest, ListReplace)
 
 TEST_F(LLVMCodeBuilderTest, GetListContents)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     std::unordered_map<List *, std::string> strings;
 
@@ -2153,11 +2151,10 @@ TEST_F(LLVMCodeBuilderTest, GetListContents)
 
 TEST_F(LLVMCodeBuilderTest, GetListItem)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     std::unordered_map<List *, std::string> strings;
 
@@ -2226,11 +2223,10 @@ TEST_F(LLVMCodeBuilderTest, GetListItem)
 
 TEST_F(LLVMCodeBuilderTest, GetListSize)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalList = std::make_shared<List>("", "");
     stage.addList(globalList);
@@ -2275,11 +2271,10 @@ TEST_F(LLVMCodeBuilderTest, GetListSize)
 
 TEST_F(LLVMCodeBuilderTest, GetListItemIndex)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalList = std::make_shared<List>("", "");
     stage.addList(globalList);
@@ -2370,11 +2365,10 @@ TEST_F(LLVMCodeBuilderTest, GetListItemIndex)
 
 TEST_F(LLVMCodeBuilderTest, ListContainsItem)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalList = std::make_shared<List>("", "");
     stage.addList(globalList);
@@ -2547,11 +2541,10 @@ TEST_F(LLVMCodeBuilderTest, Yield)
 
 TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalVar = std::make_shared<Variable>("", "", 87);
     stage.addVariable(globalVar);
@@ -2598,11 +2591,11 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
 
 TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend)
 {
-    EngineMock engine;
+
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalList1 = std::make_shared<List>("", "");
     stage.addList(globalList1);
@@ -2885,11 +2878,10 @@ TEST_F(LLVMCodeBuilderTest, IfStatement)
 
 TEST_F(LLVMCodeBuilderTest, IfStatementVariables)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalVar = std::make_shared<Variable>("", "", "test");
     stage.addVariable(globalVar);
@@ -2983,11 +2975,10 @@ TEST_F(LLVMCodeBuilderTest, IfStatementVariables)
 
 TEST_F(LLVMCodeBuilderTest, IfStatementLists)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalList1 = std::make_shared<List>("", "");
     stage.addList(globalList1);
@@ -3507,11 +3498,10 @@ TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop)
 
 TEST_F(LLVMCodeBuilderTest, LoopVariables)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalVar = std::make_shared<Variable>("", "", "test");
     stage.addVariable(globalVar);
@@ -3641,11 +3631,10 @@ TEST_F(LLVMCodeBuilderTest, LoopVariables)
 
 TEST_F(LLVMCodeBuilderTest, LoopLists)
 {
-    EngineMock engine;
     Stage stage;
     Sprite sprite;
-    sprite.setEngine(&engine);
-    EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage));
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
     auto globalList1 = std::make_shared<List>("", "");
     stage.addList(globalList1);
diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp
index 581a3a58..08427a32 100644
--- a/test/dev/llvm/llvmexecutablecode_test.cpp
+++ b/test/dev/llvm/llvmexecutablecode_test.cpp
@@ -6,8 +6,9 @@
 #include <scratchcpp/thread.h>
 #include <scratchcpp/script.h>
 #include <dev/engine/internal/llvm/llvmexecutablecode.h>
+#include <dev/engine/internal/llvm/llvmcompilercontext.h>
 #include <dev/engine/internal/llvm/llvmexecutioncontext.h>
-#include <llvm/Support/TargetSelect.h>
+#include <llvm/IR/Module.h>
 #include <llvm/IR/IRBuilder.h>
 #include <enginemock.h>
 
@@ -21,27 +22,26 @@ class LLVMExecutableCodeTest : public testing::Test
     public:
         void SetUp() override
         {
-            m_module = std::make_unique<llvm::Module>("test", m_ctx);
-            m_builder = std::make_unique<llvm::IRBuilder<>>(m_ctx);
+            m_target.setName("test");
+            m_ctx = std::make_unique<LLVMCompilerContext>(&m_engine, &m_target);
+            m_module = m_ctx->module();
+            m_llvmCtx = m_ctx->llvmCtx();
+            m_builder = std::make_unique<llvm::IRBuilder<>>(*m_llvmCtx);
             test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency
 
             m_script = std::make_unique<Script>(&m_target, nullptr, &m_engine);
-
-            llvm::InitializeNativeTarget();
-            llvm::InitializeNativeTargetAsmPrinter();
-            llvm::InitializeNativeTargetAsmParser();
         }
 
-        inline llvm::Constant *nullPointer() { return llvm::ConstantPointerNull::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0)); }
+        inline llvm::Constant *nullPointer() { return llvm::ConstantPointerNull::get(llvm::PointerType::get(llvm::Type::getInt8Ty(*m_llvmCtx), 0)); }
 
         llvm::Function *beginMainFunction()
         {
             // void *f(ExecutionContext *, Target *, ValueData **, List **)
-            llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+            llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(*m_llvmCtx), 0);
             llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType, pointerType }, false);
-            llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get());
+            llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module);
 
-            llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
+            llvm::BasicBlock *entry = llvm::BasicBlock::Create(*m_llvmCtx, "entry", func);
             m_builder->SetInsertPoint(entry);
             return func;
         }
@@ -49,10 +49,10 @@ class LLVMExecutableCodeTest : public testing::Test
         llvm::Function *beginResumeFunction()
         {
             // bool f(void *)
-            llvm::FunctionType *funcType = llvm::FunctionType::get(m_builder->getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), false);
-            llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "resume", m_module.get());
+            llvm::FunctionType *funcType = llvm::FunctionType::get(m_builder->getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(*m_llvmCtx), 0), false);
+            llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "resume", m_module);
 
-            llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func);
+            llvm::BasicBlock *entry = llvm::BasicBlock::Create(*m_llvmCtx, "entry", func);
             m_builder->SetInsertPoint(entry);
             return func;
         }
@@ -61,10 +61,10 @@ class LLVMExecutableCodeTest : public testing::Test
 
         void addTestFunction(llvm::Function *mainFunc)
         {
-            auto ptrType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+            auto ptrType = llvm::PointerType::get(llvm::Type::getInt8Ty(*m_llvmCtx), 0);
             auto func = m_module->getOrInsertFunction("test_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType, ptrType, ptrType, ptrType }, false));
 
-            llvm::Constant *mockInt = llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_ctx), (uintptr_t)&m_mock, false);
+            llvm::Constant *mockInt = llvm::ConstantInt::get(llvm::Type::getInt64Ty(*m_llvmCtx), (uintptr_t)&m_mock, false);
             llvm::Constant *mockPtr = llvm::ConstantExpr::getIntToPtr(mockInt, ptrType);
 
             m_builder->CreateCall(func, { mockPtr, mainFunc->getArg(0), mainFunc->getArg(1), mainFunc->getArg(2), mainFunc->getArg(3) });
@@ -72,13 +72,14 @@ class LLVMExecutableCodeTest : public testing::Test
 
         void addTestPrintFunction(llvm::Value *arg1, llvm::Value *arg2)
         {
-            auto ptrType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0);
+            auto ptrType = llvm::PointerType::get(llvm::Type::getInt8Ty(*m_llvmCtx), 0);
             auto func = m_module->getOrInsertFunction("test_print_function", llvm::FunctionType::get(m_builder->getVoidTy(), { ptrType, ptrType }, false));
             m_builder->CreateCall(func, { arg1, arg2 });
         }
 
-        llvm::LLVMContext m_ctx;
-        std::unique_ptr<llvm::Module> m_module;
+        std::unique_ptr<LLVMCompilerContext> m_ctx;
+        llvm::LLVMContext *m_llvmCtx = nullptr;
+        llvm::Module *m_module = nullptr;
         std::unique_ptr<llvm::IRBuilder<>> m_builder;
         Target m_target;
         EngineMock m_engine;
@@ -88,13 +89,13 @@ class LLVMExecutableCodeTest : public testing::Test
 
 TEST_F(LLVMExecutableCodeTest, CreateExecutionContext)
 {
-    beginMainFunction();
+    llvm::Function *mainFunc = beginMainFunction();
     endFunction(nullPointer());
 
-    beginResumeFunction();
+    llvm::Function *resumeFunc = beginResumeFunction();
     endFunction(m_builder->getInt1(true));
 
-    auto code = std::make_shared<LLVMExecutableCode>(std::move(m_module));
+    auto code = std::make_shared<LLVMExecutableCode>(m_ctx.get(), mainFunc->getName().str(), resumeFunc->getName().str());
     m_script->setCode(code);
     Thread thread(&m_target, &m_engine, m_script.get());
     auto ctx = code->createExecutionContext(&thread);
@@ -108,14 +109,14 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
     m_target.addVariable(std::make_shared<Variable>("", ""));
     m_target.addList(std::make_shared<List>("", ""));
 
-    auto f = beginMainFunction();
-    addTestFunction(f);
+    llvm::Function *mainFunc = beginMainFunction();
+    addTestFunction(mainFunc);
     endFunction(nullPointer());
 
-    beginResumeFunction();
+    llvm::Function *resumeFunc = beginResumeFunction();
     endFunction(m_builder->getInt1(true));
 
-    auto code = std::make_shared<LLVMExecutableCode>(std::move(m_module));
+    auto code = std::make_shared<LLVMExecutableCode>(m_ctx.get(), mainFunc->getName().str(), resumeFunc->getName().str());
     m_script->setCode(code);
     Thread thread(&m_target, &m_engine, m_script.get());
     auto ctx = code->createExecutionContext(&thread);
@@ -161,14 +162,14 @@ TEST_F(LLVMExecutableCodeTest, MainFunction)
 
 TEST_F(LLVMExecutableCodeTest, Promise)
 {
-    auto f = beginMainFunction();
-    addTestFunction(f);
+    llvm::Function *mainFunc = beginMainFunction();
+    addTestFunction(mainFunc);
     endFunction(nullPointer());
 
-    beginResumeFunction();
+    llvm::Function *resumeFunc = beginResumeFunction();
     endFunction(m_builder->getInt1(true));
 
-    auto code = std::make_shared<LLVMExecutableCode>(std::move(m_module));
+    auto code = std::make_shared<LLVMExecutableCode>(m_ctx.get(), mainFunc->getName().str(), resumeFunc->getName().str());
     m_script->setCode(code);
     Thread thread(&m_target, &m_engine, m_script.get());
     auto ctx = code->createExecutionContext(&thread);
diff --git a/test/mocks/codebuilderfactorymock.h b/test/mocks/codebuilderfactorymock.h
index c561cc84..f04d8b0a 100644
--- a/test/mocks/codebuilderfactorymock.h
+++ b/test/mocks/codebuilderfactorymock.h
@@ -8,5 +8,6 @@ using namespace libscratchcpp;
 class CodeBuilderFactoryMock : public ICodeBuilderFactory
 {
     public:
-        MOCK_METHOD(std::shared_ptr<ICodeBuilder>, create, (Target *, const std::string &, bool), (const, override));
+        MOCK_METHOD(std::shared_ptr<ICodeBuilder>, create, (CompilerContext *, bool), (const, override));
+        MOCK_METHOD(std::shared_ptr<CompilerContext>, createCtx, (IEngine *, Target *), (const, override));
 };
diff --git a/test/scratch_classes/inputvalue_test.cpp b/test/scratch_classes/inputvalue_test.cpp
index 3cf6cc3f..1b366645 100644
--- a/test/scratch_classes/inputvalue_test.cpp
+++ b/test/scratch_classes/inputvalue_test.cpp
@@ -130,7 +130,7 @@ TEST(InputValueTest, Compile)
     Compiler compiler(&engine, &target);
 
     auto block = std::make_shared<Block>("", "");
-    EXPECT_CALL(builderFactory, create(_, _, _)).WillOnce(Return(builder));
+    EXPECT_CALL(builderFactory, create).WillOnce(Return(builder));
     EXPECT_CALL(*builder, finalize);
     compiler.compile(block);
     CompilerPrivate::builderFactory = nullptr;

From a3aa0f832e6f2e079252c4210e1723729e8eef68 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 12:41:02 +0100
Subject: [PATCH 198/259] Implement LLVM procedures

---
 src/dev/engine/compiler.cpp                   |   2 +-
 .../engine/internal/codebuilderfactory.cpp    |   4 +-
 src/dev/engine/internal/codebuilderfactory.h  |   2 +-
 src/dev/engine/internal/icodebuilder.h        |   3 +
 src/dev/engine/internal/icodebuilderfactory.h |   3 +-
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 166 +++++++++++++++---
 .../engine/internal/llvm/llvmcodebuilder.h    |  12 +-
 .../engine/internal/llvm/llvminstruction.h    |   6 +-
 test/dev/compiler/compiler_test.cpp           |   4 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 104 ++++++++++-
 test/mocks/codebuilderfactorymock.h           |   2 +-
 test/mocks/codebuildermock.h                  |   2 +
 12 files changed, 277 insertions(+), 33 deletions(-)

diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index b8e0ff82..f9759fa4 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -46,7 +46,7 @@ std::shared_ptr<libscratchcpp::Block> Compiler::block() const
 /*! Compiles the script starting with the given block. */
 std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBlock)
 {
-    impl->builder = impl->builderFactory->create(impl->ctx, false);
+    impl->builder = impl->builderFactory->create(impl->ctx, nullptr);
     impl->substackTree.clear();
     impl->substackHit = false;
     impl->emptySubstack = false;
diff --git a/src/dev/engine/internal/codebuilderfactory.cpp b/src/dev/engine/internal/codebuilderfactory.cpp
index 1623c67b..bb3cb1e9 100644
--- a/src/dev/engine/internal/codebuilderfactory.cpp
+++ b/src/dev/engine/internal/codebuilderfactory.cpp
@@ -13,10 +13,10 @@ std::shared_ptr<CodeBuilderFactory> CodeBuilderFactory::instance()
     return m_instance;
 }
 
-std::shared_ptr<ICodeBuilder> CodeBuilderFactory::create(CompilerContext *ctx, bool warp) const
+std::shared_ptr<ICodeBuilder> CodeBuilderFactory::create(CompilerContext *ctx, BlockPrototype *procedurePrototype) const
 {
     assert(dynamic_cast<LLVMCompilerContext *>(ctx));
-    return std::make_shared<LLVMCodeBuilder>(static_cast<LLVMCompilerContext *>(ctx), warp);
+    return std::make_shared<LLVMCodeBuilder>(static_cast<LLVMCompilerContext *>(ctx), procedurePrototype);
 }
 
 std::shared_ptr<CompilerContext> CodeBuilderFactory::createCtx(IEngine *engine, Target *target) const
diff --git a/src/dev/engine/internal/codebuilderfactory.h b/src/dev/engine/internal/codebuilderfactory.h
index e8eea9c9..942a9a1e 100644
--- a/src/dev/engine/internal/codebuilderfactory.h
+++ b/src/dev/engine/internal/codebuilderfactory.h
@@ -11,7 +11,7 @@ class CodeBuilderFactory : public ICodeBuilderFactory
 {
     public:
         static std::shared_ptr<CodeBuilderFactory> instance();
-        std::shared_ptr<ICodeBuilder> create(CompilerContext *ctx, bool warp) const override;
+        std::shared_ptr<ICodeBuilder> create(CompilerContext *ctx, BlockPrototype *procedurePrototype) const override;
         std::shared_ptr<CompilerContext> createCtx(IEngine *engine, Target *target) const override;
 
     private:
diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index a3d38629..2c37af44 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -11,6 +11,7 @@ class Value;
 class Variable;
 class List;
 class ExecutableCode;
+class BlockPrototype;
 
 class ICodeBuilder
 {
@@ -91,6 +92,8 @@ class ICodeBuilder
         virtual void yield() = 0;
 
         virtual void createStop() = 0;
+
+        virtual void createProcedureCall(BlockPrototype *prototype) = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/icodebuilderfactory.h b/src/dev/engine/internal/icodebuilderfactory.h
index 75b485c2..6e031b2d 100644
--- a/src/dev/engine/internal/icodebuilderfactory.h
+++ b/src/dev/engine/internal/icodebuilderfactory.h
@@ -9,6 +9,7 @@ namespace libscratchcpp
 
 class ICodeBuilder;
 class CompilerContext;
+class BlockPrototype;
 class Target;
 class IEngine;
 
@@ -17,7 +18,7 @@ class ICodeBuilderFactory
     public:
         virtual ~ICodeBuilderFactory() { }
 
-        virtual std::shared_ptr<ICodeBuilder> create(CompilerContext *ctx, bool warp) const = 0;
+        virtual std::shared_ptr<ICodeBuilder> create(CompilerContext *ctx, BlockPrototype *procedurePrototype = nullptr) const = 0;
         virtual std::shared_ptr<CompilerContext> createCtx(IEngine *engine, Target *target) const = 0;
 };
 
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index a4e2eec4..d8a88265 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -8,6 +8,7 @@
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
+#include <scratchcpp/blockprototype.h>
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/dev/compilerlocalvariable.h>
 
@@ -24,14 +25,15 @@ using namespace libscratchcpp;
 static std::unordered_map<ValueType, Compiler::StaticType>
     TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } };
 
-LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, bool warp) :
+LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *procedurePrototype) :
     m_ctx(ctx),
     m_target(ctx->target()),
     m_llvmCtx(*ctx->llvmCtx()),
     m_module(ctx->module()),
     m_builder(m_llvmCtx),
-    m_defaultWarp(warp),
-    m_warp(warp)
+    m_procedurePrototype(procedurePrototype),
+    m_defaultWarp(procedurePrototype ? procedurePrototype->warp() : false),
+    m_warp(m_defaultWarp)
 {
     initTypes();
     createVariableMap();
@@ -40,9 +42,11 @@ LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, bool warp) :
 
 std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 {
-    // Do not create coroutine if there are no yield instructions
     if (!m_warp) {
-        auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const LLVMInstruction &step) { return step.type == LLVMInstruction::Type::Yield; });
+        // Do not create coroutine if there are no yield instructions nor non-warp procedure calls
+        auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const LLVMInstruction &step) {
+            return step.type == LLVMInstruction::Type::Yield || (step.type == LLVMInstruction::Type::CallProcedure && step.procedurePrototype && !step.procedurePrototype->warp());
+        });
 
         if (it == m_instructions.end())
             m_warp = true;
@@ -57,14 +61,25 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     m_builder.setFastMathFlags(fmf);
 
     // Create function
-    // void *f(ExecutionContext *, Target *, ValueData **, List **)
-    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
-    llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, { pointerType, pointerType, pointerType, pointerType }, false);
-    llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module);
+    std::string funcName = getMainFunctionName(m_procedurePrototype);
+    llvm::FunctionType *funcType = getMainFunctionType(m_procedurePrototype);
+    llvm::Function *func;
+
+    if (m_procedurePrototype)
+        func = getOrCreateFunction(funcName, funcType);
+    else
+        func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, funcName, m_module);
+
     llvm::Value *executionContextPtr = func->getArg(0);
     llvm::Value *targetPtr = func->getArg(1);
     llvm::Value *targetVariables = func->getArg(2);
     llvm::Value *targetLists = func->getArg(3);
+    llvm::Value *warpArg = nullptr;
+
+    if (m_procedurePrototype) {
+        func->addFnAttr(llvm::Attribute::AlwaysInline);
+        warpArg = func->getArg(4);
+    }
 
     llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", func);
     llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", func);
@@ -787,15 +802,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             }
 
             case LLVMInstruction::Type::Yield:
-                if (!m_warp) {
-                    // TODO: Do not allow use after suspend (use after free)
-                    freeScopeHeap();
-                    syncVariables(targetVariables);
-                    coro->createSuspend();
-                    reloadVariables(targetVariables);
-                    reloadLists();
-                }
-
+                // TODO: Do not allow use after suspend (use after free)
+                createSuspend(coro.get(), func, warpArg, targetVariables);
                 break;
 
             case LLVMInstruction::Type::BeginIf: {
@@ -1019,6 +1027,47 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.SetInsertPoint(nextBranch);
                 break;
             }
+
+            case LLVMInstruction::Type::CallProcedure: {
+                assert(step.procedurePrototype);
+                freeScopeHeap();
+                syncVariables(targetVariables);
+
+                std::string name = getMainFunctionName(step.procedurePrototype);
+                llvm::FunctionType *type = getMainFunctionType(step.procedurePrototype);
+                std::vector<llvm::Value *> args;
+                const size_t argCount = type->getNumParams() - 1 - step.procedurePrototype->argumentTypes().size(); // omit warp arg and procedure args
+
+                for (size_t i = 0; i < argCount; i++)
+                    args.push_back(func->getArg(i));
+
+                // Add warp arg
+                if (m_warp)
+                    args.push_back(m_builder.getInt1(true));
+                else
+                    args.push_back(m_procedurePrototype ? warpArg : m_builder.getInt1(false));
+
+                // TODO: Add procedure args
+                llvm::Value *handle = m_builder.CreateCall(resolveFunction(name, type), args);
+
+                if (!m_warp && !step.procedurePrototype->warp()) {
+                    llvm::BasicBlock *suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                    llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                    m_builder.CreateCondBr(m_builder.CreateIsNull(handle), nextBranch, suspendBranch);
+
+                    m_builder.SetInsertPoint(suspendBranch);
+                    createSuspend(coro.get(), func, warpArg, targetVariables);
+                    name = getResumeFunctionName(step.procedurePrototype);
+                    llvm::Value *done = m_builder.CreateCall(resolveFunction(name, m_resumeFuncType), { handle });
+                    m_builder.CreateCondBr(done, nextBranch, suspendBranch);
+
+                    m_builder.SetInsertPoint(nextBranch);
+                }
+
+                reloadVariables(targetVariables);
+                reloadLists();
+                break;
+            }
         }
     }
 
@@ -1030,6 +1079,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     syncVariables(targetVariables);
 
     // End and verify the function
+    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
+
     if (m_warp)
         m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType));
     else
@@ -1039,8 +1090,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
     // Create resume function
     // bool resume(void *)
-    funcType = llvm::FunctionType::get(m_builder.getInt1Ty(), pointerType, false);
-    llvm::Function *resumeFunc = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "resume", m_module);
+    funcName = getResumeFunctionName(m_procedurePrototype);
+    llvm::Function *resumeFunc = getOrCreateFunction(funcName, m_resumeFuncType);
 
     entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", resumeFunc);
     m_builder.SetInsertPoint(entry);
@@ -1451,9 +1502,18 @@ void LLVMCodeBuilder::createStop()
     m_instructions.push_back({ LLVMInstruction::Type::Stop });
 }
 
+void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype)
+{
+    LLVMInstruction ins(LLVMInstruction::Type::CallProcedure);
+    ins.procedurePrototype = prototype;
+    m_instructions.push_back(ins);
+}
+
 void LLVMCodeBuilder::initTypes()
 {
+    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
     m_valueDataType = LLVMTypes::createValueDataType(&m_builder);
+    m_resumeFuncType = llvm::FunctionType::get(m_builder.getInt1Ty(), pointerType, false);
 }
 
 void LLVMCodeBuilder::createVariableMap()
@@ -1557,6 +1617,47 @@ void LLVMCodeBuilder::popScopeLevel()
     m_heap.pop_back();
 }
 
+std::string LLVMCodeBuilder::getMainFunctionName(BlockPrototype *procedurePrototype)
+{
+    return procedurePrototype ? "f." + procedurePrototype->procCode() : "f";
+}
+
+std::string LLVMCodeBuilder::getResumeFunctionName(BlockPrototype *procedurePrototype)
+{
+    return procedurePrototype ? "resume." + procedurePrototype->procCode() : "resume";
+}
+
+llvm::FunctionType *LLVMCodeBuilder::getMainFunctionType(BlockPrototype *procedurePrototype)
+{
+    // void *f(ExecutionContext *, Target *, ValueData **, List **, (warp arg), (procedure args...))
+    llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
+    std::vector<llvm::Type *> argTypes = { pointerType, pointerType, pointerType, pointerType };
+
+    if (procedurePrototype) {
+        argTypes.push_back(m_builder.getInt1Ty()); // warp arg (only in procedures)
+        const auto &types = procedurePrototype->argumentTypes();
+
+        for (BlockPrototype::ArgType type : types) {
+            if (type == BlockPrototype::ArgType::Bool)
+                argTypes.push_back(m_builder.getInt1Ty());
+            else
+                argTypes.push_back(m_valueDataType->getPointerTo());
+        }
+    }
+
+    return llvm::FunctionType::get(pointerType, argTypes, false);
+}
+
+llvm::Function *LLVMCodeBuilder::getOrCreateFunction(const std::string &name, llvm::FunctionType *type)
+{
+    llvm::Function *func = m_module->getFunction(name);
+
+    if (func)
+        return func;
+    else
+        return llvm::Function::Create(type, llvm::Function::ExternalLinkage, name, m_module);
+}
+
 void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
 {
     if (llvm::verifyFunction(*func, &llvm::errs())) {
@@ -2346,6 +2447,31 @@ llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegister *arg1, LLVMRegister
     }
 }
 
+void LLVMCodeBuilder::createSuspend(LLVMCoroutine *coro, llvm::Function *func, llvm::Value *warpArg, llvm::Value *targetVariables)
+{
+    if (!m_warp) {
+        llvm::BasicBlock *suspendBranch, *nextBranch;
+
+        if (warpArg) {
+            suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+            nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+            m_builder.CreateCondBr(warpArg, nextBranch, suspendBranch);
+            m_builder.SetInsertPoint(suspendBranch);
+        }
+
+        freeScopeHeap();
+        syncVariables(targetVariables);
+        coro->createSuspend();
+        reloadVariables(targetVariables);
+        reloadLists();
+
+        if (warpArg) {
+            m_builder.CreateBr(nextBranch);
+            m_builder.SetInsertPoint(nextBranch);
+        }
+    }
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolveFunction(const std::string name, llvm::FunctionType *type)
 {
     return m_module->getOrInsertFunction(name, type);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index f504050a..030bb4fa 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -22,7 +22,7 @@ class LLVMConstantRegister;
 class LLVMCodeBuilder : public ICodeBuilder
 {
     public:
-        LLVMCodeBuilder(LLVMCompilerContext *ctx, bool warp);
+        LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *procedurePrototype = nullptr);
 
         std::shared_ptr<ExecutableCode> finalize() override;
 
@@ -99,6 +99,8 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void createStop() override;
 
+        void createProcedureCall(BlockPrototype *prototype) override;
+
     private:
         enum class Comparison
         {
@@ -113,6 +115,10 @@ class LLVMCodeBuilder : public ICodeBuilder
         void pushScopeLevel();
         void popScopeLevel();
 
+        std::string getMainFunctionName(BlockPrototype *procedurePrototype);
+        std::string getResumeFunctionName(BlockPrototype *procedurePrototype);
+        llvm::FunctionType *getMainFunctionType(BlockPrototype *procedurePrototype);
+        llvm::Function *getOrCreateFunction(const std::string &name, llvm::FunctionType *type);
         void verifyFunction(llvm::Function *func);
         void optimize();
 
@@ -147,6 +153,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *createValue(LLVMRegister *reg);
         llvm::Value *createComparison(LLVMRegister *arg1, LLVMRegister *arg2, Comparison type);
 
+        void createSuspend(LLVMCoroutine *coro, llvm::Function *func, llvm::Value *warpArg, llvm::Value *targetVariables);
+
         llvm::FunctionCallee resolveFunction(const std::string name, llvm::FunctionType *type);
         llvm::FunctionCallee resolve_value_init();
         llvm::FunctionCallee resolve_value_free();
@@ -196,10 +204,12 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::IRBuilder<> m_builder;
 
         llvm::StructType *m_valueDataType = nullptr;
+        llvm::FunctionType *m_resumeFuncType = nullptr;
 
         std::vector<LLVMInstruction> m_instructions;
         std::vector<std::shared_ptr<LLVMRegister>> m_regs;
         std::vector<std::shared_ptr<CompilerLocalVariable>> m_localVars;
+        BlockPrototype *m_procedurePrototype = nullptr;
         bool m_defaultWarp = false;
         bool m_warp = false;
 
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index f4518142..7a1cda58 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -9,6 +9,8 @@
 namespace libscratchcpp
 {
 
+class BlockPrototype;
+
 struct LLVMInstruction
 {
         enum class Type
@@ -68,7 +70,8 @@ struct LLVMInstruction
             BeginRepeatUntilLoop,
             BeginLoopCondition,
             EndLoop,
-            Stop
+            Stop,
+            CallProcedure
         };
 
         LLVMInstruction(Type type) :
@@ -84,6 +87,7 @@ struct LLVMInstruction
         bool functionCtxArg = false;      // whether to add execution context ptr to function parameters
         Variable *workVariable = nullptr; // for variables
         List *workList = nullptr;         // for lists
+        BlockPrototype *procedurePrototype = nullptr;
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 22241e08..57a89e96 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -48,8 +48,8 @@ class CompilerTest : public testing::Test
         void compile(Compiler *compiler, std::shared_ptr<Block> block)
         {
             ASSERT_EQ(compiler->block(), nullptr);
-            // TODO: Test warp
-            EXPECT_CALL(m_builderFactory, create(m_ctx.get(), false)).WillOnce(Return(m_builder));
+            // TODO: Test procedures
+            EXPECT_CALL(m_builderFactory, create(m_ctx.get(), nullptr)).WillOnce(Return(m_builder));
             EXPECT_CALL(*m_builder, finalize()).WillOnce(Return(m_code));
             ASSERT_EQ(compiler->compile(block), m_code);
             ASSERT_EQ(compiler->block(), nullptr);
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 2cc1141d..ae256373 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -7,6 +7,7 @@
 #include <scratchcpp/stage.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
+#include <scratchcpp/blockprototype.h>
 #include <dev/engine/internal/llvm/llvmcodebuilder.h>
 #include <dev/engine/internal/llvm/llvmcompilercontext.h>
 #include <gmock/gmock.h>
@@ -61,10 +62,19 @@ class LLVMCodeBuilderTest : public testing::Test
             test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency
         }
 
+        void createBuilder(Target *target, BlockPrototype *procedurePrototype)
+        {
+            if (m_contexts.find(target) == m_contexts.cend() || !target)
+                m_contexts[target] = std::make_unique<LLVMCompilerContext>(&m_engine, target);
+
+            m_builder = std::make_unique<LLVMCodeBuilder>(m_contexts[target].get(), procedurePrototype);
+        }
+
         void createBuilder(Target *target, bool warp)
         {
-            m_ctx = std::make_unique<LLVMCompilerContext>(&m_engine, target);
-            m_builder = std::make_unique<LLVMCodeBuilder>(m_ctx.get(), warp);
+            m_procedurePrototype = std::make_shared<BlockPrototype>("test");
+            m_procedurePrototype->setWarp(warp);
+            createBuilder(target, m_procedurePrototype.get());
         }
 
         void createBuilder(bool warp) { createBuilder(nullptr, warp); }
@@ -359,8 +369,9 @@ class LLVMCodeBuilderTest : public testing::Test
             ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes << v.toString() << quotes;
         };
 
-        std::unique_ptr<LLVMCompilerContext> m_ctx;
+        std::unordered_map<Target *, std::unique_ptr<LLVMCompilerContext>> m_contexts;
         std::unique_ptr<LLVMCodeBuilder> m_builder;
+        std::shared_ptr<BlockPrototype> m_procedurePrototype;
         EngineMock m_engine;
         TargetMock m_target; // NOTE: isStage() is used for call expectations
         RandomGeneratorMock m_rng;
@@ -3890,3 +3901,90 @@ TEST_F(LLVMCodeBuilderTest, StopAndReturn)
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
+
+TEST_F(LLVMCodeBuilderTest, Procedures)
+{
+    Sprite sprite;
+    auto var = std::make_shared<Variable>("", "");
+    auto list = std::make_shared<List>("", "");
+    sprite.addVariable(var);
+    sprite.addList(list);
+
+    // Procedure 1
+    BlockPrototype prototype1;
+    prototype1.setProcCode("procedure 1");
+    prototype1.setWarp(false);
+    createBuilder(&sprite, &prototype1);
+
+    CompilerValue *v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
+    m_builder->endLoop();
+
+    m_builder->createVariableWrite(var.get(), m_builder->addConstValue("test"));
+    m_builder->createListClear(list.get());
+    m_builder->createListAppend(list.get(), m_builder->addConstValue("hello world"));
+
+    v = m_builder->addVariableValue(var.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    v = m_builder->addListItem(list.get(), m_builder->addConstValue(0));
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    m_builder->finalize();
+
+    // Procedure 2
+    BlockPrototype prototype2;
+    prototype2.setProcCode("procedure 2");
+    prototype2.setWarp(true);
+    createBuilder(&sprite, &prototype2);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    m_builder->createProcedureCall(&prototype1);
+    m_builder->endLoop();
+
+    m_builder->finalize();
+
+    // Script
+    createBuilder(&sprite, false);
+    m_builder->createProcedureCall(&prototype1);
+    m_builder->createProcedureCall(&prototype2);
+
+    std::string expected1 = "no_args\n";
+
+    std::string expected2 =
+        "test\n"
+        "hello world\n";
+
+    std::string expected3 =
+        "no_args\n"
+        "no_args\n"
+        "test\n"
+        "hello world\n"
+        "no_args\n"
+        "no_args\n"
+        "test\n"
+        "hello world\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1);
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1);
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected2 + expected3);
+    ASSERT_TRUE(code->isFinished(ctx.get()));
+}
diff --git a/test/mocks/codebuilderfactorymock.h b/test/mocks/codebuilderfactorymock.h
index f04d8b0a..48fcf4c7 100644
--- a/test/mocks/codebuilderfactorymock.h
+++ b/test/mocks/codebuilderfactorymock.h
@@ -8,6 +8,6 @@ using namespace libscratchcpp;
 class CodeBuilderFactoryMock : public ICodeBuilderFactory
 {
     public:
-        MOCK_METHOD(std::shared_ptr<ICodeBuilder>, create, (CompilerContext *, bool), (const, override));
+        MOCK_METHOD(std::shared_ptr<ICodeBuilder>, create, (CompilerContext *, BlockPrototype *), (const, override));
         MOCK_METHOD(std::shared_ptr<CompilerContext>, createCtx, (IEngine *, Target *), (const, override));
 };
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 2862bea8..b67f0078 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -81,4 +81,6 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(void, yield, (), (override));
 
         MOCK_METHOD(void, createStop, (), (override));
+
+        MOCK_METHOD(void, createProcedureCall, (BlockPrototype *), (override));
 };

From 70611b20831c88e06be6a2a873ae62754bdfecfc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 13:11:09 +0100
Subject: [PATCH 199/259] LLVMCodeBuilder: Add test for multiple scripts

---
 test/dev/llvm/llvmcodebuilder_test.cpp | 39 ++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index ae256373..540a6b39 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -3902,6 +3902,45 @@ TEST_F(LLVMCodeBuilderTest, StopAndReturn)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, MultipleScripts)
+{
+    Sprite sprite;
+
+    // Script 1
+    createBuilder(&sprite, nullptr);
+
+    CompilerValue *v = m_builder->addConstValue("script1");
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    auto code1 = m_builder->finalize();
+
+    // Script 2
+    createBuilder(&sprite, nullptr);
+
+    v = m_builder->addConstValue("script2");
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    auto code2 = m_builder->finalize();
+
+    Script script1(&sprite, nullptr, nullptr);
+    script1.setCode(code1);
+    Thread thread1(&sprite, nullptr, &script1);
+    auto ctx = code1->createExecutionContext(&thread1);
+
+    testing::internal::CaptureStdout();
+    code1->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "script1\n");
+
+    Script script2(&sprite, nullptr, nullptr);
+    script2.setCode(code2);
+    Thread thread2(&sprite, nullptr, &script2);
+    ctx = code2->createExecutionContext(&thread2);
+
+    testing::internal::CaptureStdout();
+    code2->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "script2\n");
+}
+
 TEST_F(LLVMCodeBuilderTest, Procedures)
 {
     Sprite sprite;

From 1c3dca5fead2cf108b06233dffd52dd7a4ea9cd8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 19:55:41 +0100
Subject: [PATCH 200/259] LLVMCodeBuilder: Implement procedure arguments

---
 src/dev/engine/internal/icodebuilder.h        |  3 +-
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 66 +++++++++++++++++--
 .../engine/internal/llvm/llvmcodebuilder.h    |  7 +-
 .../engine/internal/llvm/llvminstruction.h    |  4 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 34 ++++++----
 test/mocks/codebuildermock.h                  |  3 +-
 6 files changed, 96 insertions(+), 21 deletions(-)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 2c37af44..9f6cb567 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -32,6 +32,7 @@ class ICodeBuilder
         virtual CompilerValue *addListItemIndex(List *list, CompilerValue *item) = 0;
         virtual CompilerValue *addListContains(List *list, CompilerValue *item) = 0;
         virtual CompilerValue *addListSize(List *list) = 0;
+        virtual CompilerValue *addProcedureArgument(const std::string &name) = 0;
 
         virtual CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2) = 0;
@@ -93,7 +94,7 @@ class ICodeBuilder
 
         virtual void createStop() = 0;
 
-        virtual void createProcedureCall(BlockPrototype *prototype) = 0;
+        virtual void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index d8a88265..81ed839a 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -8,7 +8,6 @@
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
-#include <scratchcpp/blockprototype.h>
 #include <scratchcpp/dev/compilerconstant.h>
 #include <scratchcpp/dev/compilerlocalvariable.h>
 
@@ -38,6 +37,9 @@ LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *proce
     initTypes();
     createVariableMap();
     createListMap();
+
+    llvm::FunctionType *funcType = getMainFunctionType(nullptr);
+    m_defaultArgCount = funcType->getNumParams();
 }
 
 std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
@@ -1030,15 +1032,15 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
             case LLVMInstruction::Type::CallProcedure: {
                 assert(step.procedurePrototype);
+                assert(step.args.size() == step.procedurePrototype->argumentTypes().size());
                 freeScopeHeap();
                 syncVariables(targetVariables);
 
                 std::string name = getMainFunctionName(step.procedurePrototype);
                 llvm::FunctionType *type = getMainFunctionType(step.procedurePrototype);
                 std::vector<llvm::Value *> args;
-                const size_t argCount = type->getNumParams() - 1 - step.procedurePrototype->argumentTypes().size(); // omit warp arg and procedure args
 
-                for (size_t i = 0; i < argCount; i++)
+                for (size_t i = 0; i < m_defaultArgCount; i++)
                     args.push_back(func->getArg(i));
 
                 // Add warp arg
@@ -1047,7 +1049,14 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 else
                     args.push_back(m_procedurePrototype ? warpArg : m_builder.getInt1(false));
 
-                // TODO: Add procedure args
+                // Add procedure args
+                for (const auto &arg : step.args) {
+                    if (arg.first == Compiler::StaticType::Unknown)
+                        args.push_back(createValue(arg.second));
+                    else
+                        args.push_back(castValue(arg.second, arg.first));
+                }
+
                 llvm::Value *handle = m_builder.CreateCall(resolveFunction(name, type), args);
 
                 if (!m_warp && !step.procedurePrototype->warp()) {
@@ -1068,6 +1077,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 reloadLists();
                 break;
             }
+
+            case LLVMInstruction::Type::ProcedureArg: {
+                assert(m_procedurePrototype);
+                llvm::Value *arg = func->getArg(m_defaultArgCount + 1 + step.procedureArgIndex); // omit warp arg
+                step.functionReturnReg->value = arg;
+                break;
+            }
         }
     }
 
@@ -1230,6 +1246,31 @@ CompilerValue *LLVMCodeBuilder::addListSize(List *list)
     return createOp(ins, Compiler::StaticType::Number);
 }
 
+CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name)
+{
+    if (!m_procedurePrototype)
+        return addConstValue(Value());
+
+    const auto &argNames = m_procedurePrototype->argumentNames();
+    auto it = std::find(argNames.begin(), argNames.end(), name);
+
+    if (it == argNames.end()) {
+        std::cout << "warning: could not find argument '" << name << "' in custom block '" << m_procedurePrototype->procCode() << "'" << std::endl;
+        return addConstValue(Value());
+    }
+
+    const auto index = it - argNames.begin();
+    const Compiler::StaticType type = getProcedureArgType(m_procedurePrototype->argumentTypes()[index]);
+    LLVMInstruction ins(LLVMInstruction::Type::ProcedureArg);
+    auto ret = std::make_shared<LLVMRegister>(type);
+    ret->isRawValue = (type != Compiler::StaticType::Unknown);
+    ins.functionReturnReg = ret.get();
+    ins.procedureArgIndex = index;
+
+    m_instructions.push_back(ins);
+    return addReg(ret);
+}
+
 CompilerValue *LLVMCodeBuilder::createAdd(CompilerValue *operand1, CompilerValue *operand2)
 {
     return createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 });
@@ -1502,11 +1543,19 @@ void LLVMCodeBuilder::createStop()
     m_instructions.push_back({ LLVMInstruction::Type::Stop });
 }
 
-void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype)
+void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args)
 {
+    assert(prototype);
+    assert(prototype->argumentTypes().size() == args.size());
+    const auto &procedureArgs = prototype->argumentTypes();
+    Compiler::ArgTypes types;
+
+    for (BlockPrototype::ArgType type : procedureArgs)
+        types.push_back(getProcedureArgType(type));
+
     LLVMInstruction ins(LLVMInstruction::Type::CallProcedure);
     ins.procedurePrototype = prototype;
-    m_instructions.push_back(ins);
+    createOp(ins, Compiler::StaticType::Void, types, args);
 }
 
 void LLVMCodeBuilder::initTypes()
@@ -1920,6 +1969,11 @@ llvm::Type *LLVMCodeBuilder::getType(Compiler::StaticType type)
     }
 }
 
+Compiler::StaticType LLVMCodeBuilder::getProcedureArgType(BlockPrototype::ArgType type)
+{
+    return type == BlockPrototype::ArgType::Bool ? Compiler::StaticType::Bool : Compiler::StaticType::Unknown;
+}
+
 llvm::Value *LLVMCodeBuilder::isNaN(llvm::Value *num)
 {
     return m_builder.CreateFCmpUNO(num, num);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 030bb4fa..4f267635 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -3,6 +3,8 @@
 #pragma once
 
 #include <scratchcpp/value.h>
+#include <scratchcpp/blockprototype.h>
+
 #include <llvm/IR/LLVMContext.h>
 #include <llvm/IR/Module.h>
 #include <llvm/IR/IRBuilder.h>
@@ -38,6 +40,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         CompilerValue *addListItemIndex(List *list, CompilerValue *item) override;
         CompilerValue *addListContains(List *list, CompilerValue *item) override;
         CompilerValue *addListSize(List *list) override;
+        CompilerValue *addProcedureArgument(const std::string &name) override;
 
         CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2) override;
@@ -99,7 +102,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         void createStop() override;
 
-        void createProcedureCall(BlockPrototype *prototype) override;
+        void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) override;
 
     private:
         enum class Comparison
@@ -131,6 +134,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType);
         Compiler::StaticType optimizeRegisterType(LLVMRegister *reg);
         llvm::Type *getType(Compiler::StaticType type);
+        Compiler::StaticType getProcedureArgType(BlockPrototype::ArgType type);
         llvm::Value *isNaN(llvm::Value *num);
         llvm::Value *removeNaN(llvm::Value *num);
 
@@ -212,6 +216,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         BlockPrototype *m_procedurePrototype = nullptr;
         bool m_defaultWarp = false;
         bool m_warp = false;
+        int m_defaultArgCount = 0;
 
         std::vector<std::vector<llvm::Value *>> m_heap; // scopes
 
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 7a1cda58..cb8b7ce5 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -71,7 +71,8 @@ struct LLVMInstruction
             BeginLoopCondition,
             EndLoop,
             Stop,
-            CallProcedure
+            CallProcedure,
+            ProcedureArg
         };
 
         LLVMInstruction(Type type) :
@@ -88,6 +89,7 @@ struct LLVMInstruction
         Variable *workVariable = nullptr; // for variables
         List *workList = nullptr;         // for lists
         BlockPrototype *procedurePrototype = nullptr;
+        size_t procedureArgIndex = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 540a6b39..a5c09433 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -3951,7 +3951,9 @@ TEST_F(LLVMCodeBuilderTest, Procedures)
 
     // Procedure 1
     BlockPrototype prototype1;
-    prototype1.setProcCode("procedure 1");
+    prototype1.setProcCode("procedure 1 %s %s %b");
+    prototype1.setArgumentNames({ "any type 1", "any type 2", "bool" });
+    prototype1.setArgumentIds({ "a", "b", "c" });
     prototype1.setWarp(false);
     createBuilder(&sprite, &prototype1);
 
@@ -3960,9 +3962,9 @@ TEST_F(LLVMCodeBuilderTest, Procedures)
     m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {});
     m_builder->endLoop();
 
-    m_builder->createVariableWrite(var.get(), m_builder->addConstValue("test"));
+    m_builder->createVariableWrite(var.get(), m_builder->addProcedureArgument("any type 1"));
     m_builder->createListClear(list.get());
-    m_builder->createListAppend(list.get(), m_builder->addConstValue("hello world"));
+    m_builder->createListAppend(list.get(), m_builder->addProcedureArgument("any type 2"));
 
     v = m_builder->addVariableValue(var.get());
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
@@ -3970,6 +3972,10 @@ TEST_F(LLVMCodeBuilderTest, Procedures)
     v = m_builder->addListItem(list.get(), m_builder->addConstValue(0));
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
+    v = m_builder->addProcedureArgument("bool");
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
     m_builder->finalize();
 
     // Procedure 2
@@ -3980,31 +3986,37 @@ TEST_F(LLVMCodeBuilderTest, Procedures)
 
     v = m_builder->addConstValue(2);
     m_builder->beginRepeatLoop(v);
-    m_builder->createProcedureCall(&prototype1);
+    m_builder->createProcedureCall(&prototype1, { m_builder->addConstValue(-652.3), m_builder->addConstValue(false), m_builder->addConstValue(true) });
     m_builder->endLoop();
 
     m_builder->finalize();
 
     // Script
     createBuilder(&sprite, false);
-    m_builder->createProcedureCall(&prototype1);
-    m_builder->createProcedureCall(&prototype2);
+    m_builder->createProcedureCall(&prototype1, { m_builder->addConstValue("test"), m_builder->addConstValue(true), m_builder->addConstValue(false) });
+    m_builder->createProcedureCall(&prototype2, {});
 
     std::string expected1 = "no_args\n";
 
     std::string expected2 =
         "test\n"
-        "hello world\n";
+        "true\n"
+        "false\n"
+        "0\n";
 
     std::string expected3 =
         "no_args\n"
         "no_args\n"
-        "test\n"
-        "hello world\n"
+        "-652.3\n"
+        "false\n"
+        "true\n"
+        "1\n"
         "no_args\n"
         "no_args\n"
-        "test\n"
-        "hello world\n";
+        "-652.3\n"
+        "false\n"
+        "true\n"
+        "1\n";
 
     auto code = m_builder->finalize();
     Script script(&sprite, nullptr, nullptr);
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index b67f0078..63455dda 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -21,6 +21,7 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(CompilerValue *, addListItemIndex, (List *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, addListContains, (List *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, addListSize, (List *), (override));
+        MOCK_METHOD(CompilerValue *, addProcedureArgument, (const std::string &), (override));
 
         MOCK_METHOD(CompilerValue *, createAdd, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createSub, (CompilerValue *, CompilerValue *), (override));
@@ -82,5 +83,5 @@ class CodeBuilderMock : public ICodeBuilder
 
         MOCK_METHOD(void, createStop, (), (override));
 
-        MOCK_METHOD(void, createProcedureCall, (BlockPrototype *), (override));
+        MOCK_METHOD(void, createProcedureCall, (BlockPrototype *, const Compiler::Args &), (override));
 };

From 931835848c07cedd30e4aea13492c4e87e2c9753 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 22:07:24 +0100
Subject: [PATCH 201/259] Move LLVM optimization to LLVMCompilerContext

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 29 -------------------
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 -
 .../internal/llvm/llvmcompilercontext.cpp     | 25 ++++++++++++++++
 3 files changed, 25 insertions(+), 30 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 81ed839a..c62322cb 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1,8 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include <llvm/IR/Verifier.h>
-#include <llvm/ExecutionEngine/Orc/LLJIT.h>
-#include <llvm/Passes/PassBuilder.h>
 
 #include <scratchcpp/stage.h>
 #include <scratchcpp/iengine.h>
@@ -1119,15 +1117,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
     verifyFunction(resumeFunc);
 
-#ifdef PRINT_LLVM_IR
-    std::cout << std::endl << "=== LLVM IR (" << m_module->getName().str() << ") ===" << std::endl;
-    m_module->print(llvm::outs(), nullptr);
-    std::cout << "==============" << std::endl << std::endl;
-#endif
-
-    // Optimize
-    optimize();
-
     return std::make_shared<LLVMExecutableCode>(m_ctx, func->getName().str(), resumeFunc->getName().str());
 }
 
@@ -1715,24 +1704,6 @@ void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
     }
 }
 
-void LLVMCodeBuilder::optimize()
-{
-    llvm::PassBuilder passBuilder;
-    llvm::LoopAnalysisManager loopAnalysisManager;
-    llvm::FunctionAnalysisManager functionAnalysisManager;
-    llvm::CGSCCAnalysisManager cGSCCAnalysisManager;
-    llvm::ModuleAnalysisManager moduleAnalysisManager;
-
-    passBuilder.registerModuleAnalyses(moduleAnalysisManager);
-    passBuilder.registerCGSCCAnalyses(cGSCCAnalysisManager);
-    passBuilder.registerFunctionAnalyses(functionAnalysisManager);
-    passBuilder.registerLoopAnalyses(loopAnalysisManager);
-    passBuilder.crossRegisterProxies(loopAnalysisManager, functionAnalysisManager, cGSCCAnalysisManager, moduleAnalysisManager);
-
-    llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3);
-    modulePassManager.run(*m_module, moduleAnalysisManager);
-}
-
 LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg)
 {
     m_regs.push_back(reg);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 4f267635..85f0beba 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -123,7 +123,6 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionType *getMainFunctionType(BlockPrototype *procedurePrototype);
         llvm::Function *getOrCreateFunction(const std::string &name, llvm::FunctionType *type);
         void verifyFunction(llvm::Function *func);
-        void optimize();
 
         LLVMRegister *addReg(std::shared_ptr<LLVMRegister> reg);
 
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index 0341bc79..ccefb872 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include <llvm/Support/TargetSelect.h>
+#include <llvm/Passes/PassBuilder.h>
 
 #include <scratchcpp/target.h>
 #include <iostream>
@@ -43,6 +44,30 @@ void LLVMCompilerContext::initJit()
     m_jitInitialized = true;
     assert(m_llvmCtx);
     assert(m_module);
+
+#ifdef PRINT_LLVM_IR
+    std::cout << std::endl << "=== LLVM IR (" << m_module->getName().str() << ") ===" << std::endl;
+    m_module->print(llvm::outs(), nullptr);
+    std::cout << "==============" << std::endl << std::endl;
+#endif
+
+    // Optimize
+    llvm::PassBuilder passBuilder;
+    llvm::LoopAnalysisManager loopAnalysisManager;
+    llvm::FunctionAnalysisManager functionAnalysisManager;
+    llvm::CGSCCAnalysisManager cGSCCAnalysisManager;
+    llvm::ModuleAnalysisManager moduleAnalysisManager;
+
+    passBuilder.registerModuleAnalyses(moduleAnalysisManager);
+    passBuilder.registerCGSCCAnalyses(cGSCCAnalysisManager);
+    passBuilder.registerFunctionAnalyses(functionAnalysisManager);
+    passBuilder.registerLoopAnalyses(loopAnalysisManager);
+    passBuilder.crossRegisterProxies(loopAnalysisManager, functionAnalysisManager, cGSCCAnalysisManager, moduleAnalysisManager);
+
+    llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3);
+    modulePassManager.run(*m_module, moduleAnalysisManager);
+
+    // Init JIT compiler
     std::string name = m_module->getName().str();
     auto err = m_jit->get()->addIRModule(llvm::orc::ThreadSafeModule(std::move(m_module), std::move(m_llvmCtx)));
 

From 8341dda93772971c86230ddf12d1143823698d52 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 22:11:06 +0100
Subject: [PATCH 202/259] LLVMCodeBuilder: Fix IR output captured in stdout

---
 test/dev/llvm/llvmcodebuilder_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index a5c09433..675f4440 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1734,12 +1734,12 @@ TEST_F(LLVMCodeBuilderTest, Select)
         "true\n";
 
     auto code = m_builder->finalize();
-    testing::internal::CaptureStdout();
     Script script(&sprite, nullptr, nullptr);
     script.setCode(code);
     ;
     Thread thread(&sprite, nullptr, &script);
     auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }

From aec52a0851387a674a80b3e4ff7d0435da89b00b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 22:12:24 +0100
Subject: [PATCH 203/259] Compiler: Make addInput(Input*) public

---
 include/scratchcpp/dev/compiler.h   |   3 +-
 src/dev/engine/compiler.cpp         | 111 ++++++++++++++--------------
 test/dev/compiler/compiler_test.cpp |   4 +-
 3 files changed, 59 insertions(+), 59 deletions(-)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 0ac77bbc..bde6b69c 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -63,6 +63,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *addListContains(List *list, CompilerValue *item);
         CompilerValue *addListSize(List *list);
         CompilerValue *addInput(const std::string &name);
+        CompilerValue *addInput(Input *input);
 
         CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2);
@@ -137,8 +138,6 @@ class LIBSCRATCHCPP_EXPORT Compiler
         static std::shared_ptr<CompilerContext> createContext(IEngine *engine, Target *target);
 
     private:
-        CompilerValue *addInput(Input *input);
-
         spimpl::unique_impl_ptr<CompilerPrivate> impl;
 };
 
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index f9759fa4..08c810f6 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -182,6 +182,62 @@ CompilerValue *Compiler::addInput(const std::string &name)
     return addInput(impl->block->inputAt(impl->block->findInput(name)).get());
 }
 
+/*! Compiles the given input and adds it to the compiled code. */
+CompilerValue *Compiler::addInput(Input *input)
+{
+    if (!input)
+        return addConstValue(Value());
+
+    switch (input->type()) {
+        case Input::Type::Shadow:
+        case Input::Type::NoShadow: {
+            if (input->pointsToDropdownMenu())
+                return addConstValue(input->selectedMenuItem());
+            else {
+                CompilerValue *ret = nullptr;
+                auto previousBlock = impl->block;
+                impl->block = input->valueBlock();
+
+                if (impl->block) {
+                    if (impl->block->compileFunction())
+                        ret = impl->block->compile(this);
+                    else {
+                        std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl;
+                        impl->unsupportedBlocks.insert(impl->block->opcode());
+                        ret = addConstValue(Value());
+                    }
+                } else
+                    ret = addConstValue(input->primaryValue()->value());
+
+                impl->block = previousBlock;
+                return ret;
+            }
+        }
+
+        case Input::Type::ObscuredShadow: {
+            CompilerValue *ret = nullptr;
+            auto previousBlock = impl->block;
+            impl->block = input->valueBlock();
+
+            if (impl->block) {
+                if (impl->block->compileFunction())
+                    ret = impl->block->compile(this);
+                else {
+                    std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl;
+                    impl->unsupportedBlocks.insert(impl->block->opcode());
+                    ret = addConstValue(Value());
+                }
+            } else
+                ret = input->primaryValue()->compile(this);
+
+            impl->block = previousBlock;
+            return ret;
+        }
+    }
+
+    return nullptr;
+}
+
 /*! Creates an add instruction. */
 CompilerValue *Compiler::createAdd(CompilerValue *operand1, CompilerValue *operand2)
 {
@@ -583,58 +639,3 @@ std::shared_ptr<CompilerContext> Compiler::createContext(IEngine *engine, Target
     CompilerPrivate::initBuilderFactory();
     return CompilerPrivate::builderFactory->createCtx(engine, target);
 }
-
-CompilerValue *Compiler::addInput(Input *input)
-{
-    if (!input)
-        return addConstValue(Value());
-
-    switch (input->type()) {
-        case Input::Type::Shadow:
-        case Input::Type::NoShadow: {
-            if (input->pointsToDropdownMenu())
-                return addConstValue(input->selectedMenuItem());
-            else {
-                CompilerValue *ret = nullptr;
-                auto previousBlock = impl->block;
-                impl->block = input->valueBlock();
-
-                if (impl->block) {
-                    if (impl->block->compileFunction())
-                        ret = impl->block->compile(this);
-                    else {
-                        std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl;
-                        impl->unsupportedBlocks.insert(impl->block->opcode());
-                        ret = addConstValue(Value());
-                    }
-                } else
-                    ret = addConstValue(input->primaryValue()->value());
-
-                impl->block = previousBlock;
-                return ret;
-            }
-        }
-
-        case Input::Type::ObscuredShadow: {
-            CompilerValue *ret = nullptr;
-            auto previousBlock = impl->block;
-            impl->block = input->valueBlock();
-
-            if (impl->block) {
-                if (impl->block->compileFunction())
-                    ret = impl->block->compile(this);
-                else {
-                    std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl;
-                    impl->unsupportedBlocks.insert(impl->block->opcode());
-                    ret = addConstValue(Value());
-                }
-            } else
-                ret = input->primaryValue()->compile(this);
-
-            impl->block = previousBlock;
-            return ret;
-        }
-    }
-
-    return nullptr;
-}
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index 57a89e96..e19d2ce6 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -418,10 +418,10 @@ TEST_F(CompilerTest, AddInput)
         EXPECT_EQ(compiler->addInput("OBSCURED_SHADOW"), &constRet);
 
         EXPECT_CALL(*m_builder, addVariableValue(m_testVar.get())).WillOnce(Return(&ret));
-        EXPECT_EQ(compiler->addInput("OBSCURED_SHADOW_VARIABLE"), &ret);
+        EXPECT_EQ(compiler->addInput(compiler->input("OBSCURED_SHADOW_VARIABLE")), &ret);
 
         EXPECT_CALL(*m_builder, addConstValue(Value("value block"))).WillOnce(Return(&constRet));
-        EXPECT_EQ(compiler->addInput("OBSCURED_SHADOW_BLOCK"), &constRet);
+        EXPECT_EQ(compiler->addInput(compiler->input("OBSCURED_SHADOW_BLOCK")), &constRet);
 
         return nullptr;
     });

From f29fbc0c1d7781b4d24e89b285157e4aa140bd02 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 22:28:58 +0100
Subject: [PATCH 204/259] Compiler: Add API for procedures

---
 include/scratchcpp/dev/compiler.h   |  5 +++
 src/dev/engine/compiler.cpp         | 28 +++++++++++-
 test/dev/compiler/compiler_test.cpp | 70 +++++++++++++++++++++++++++--
 3 files changed, 99 insertions(+), 4 deletions(-)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index bde6b69c..b362afaa 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -22,6 +22,7 @@ class Variable;
 class List;
 class Input;
 class Field;
+class BlockPrototype;
 class CompilerPrivate;
 
 /*! \brief The Compiler class provides API for compiling Scratch scripts. */
@@ -62,6 +63,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *addListItemIndex(List *list, CompilerValue *item);
         CompilerValue *addListContains(List *list, CompilerValue *item);
         CompilerValue *addListSize(List *list);
+        CompilerValue *addProcedureArgument(const std::string &name);
+
         CompilerValue *addInput(const std::string &name);
         CompilerValue *addInput(Input *input);
 
@@ -130,6 +133,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void createYield();
         void createStop();
 
+        void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args);
+
         Input *input(const std::string &name) const;
         Field *field(const std::string &name) const;
 
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 08c810f6..2ce143e8 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -46,7 +46,21 @@ std::shared_ptr<libscratchcpp::Block> Compiler::block() const
 /*! Compiles the script starting with the given block. */
 std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBlock)
 {
-    impl->builder = impl->builderFactory->create(impl->ctx, nullptr);
+    BlockPrototype *procedurePrototype = nullptr;
+
+    if (startBlock) {
+        // TODO: Move procedure definition logic to the custom blocks extension
+        auto input = startBlock->inputAt(0);
+
+        if (input && input->valueBlock()) {
+            procedurePrototype = input->valueBlock()->mutationPrototype();
+
+            if (procedurePrototype && procedurePrototype->procCode().empty())
+                procedurePrototype = nullptr;
+        }
+    }
+
+    impl->builder = impl->builderFactory->create(impl->ctx, procedurePrototype);
     impl->substackTree.clear();
     impl->substackHit = false;
     impl->emptySubstack = false;
@@ -176,6 +190,12 @@ CompilerValue *Compiler::addListSize(List *list)
     return impl->builder->addListSize(list);
 }
 
+/*! Adds the procedure argument with the given name to the code. */
+CompilerValue *Compiler::addProcedureArgument(const std::string &name)
+{
+    return impl->builder->addProcedureArgument(name);
+}
+
 /*! Compiles the given input (resolved by name) and adds it to the compiled code. */
 CompilerValue *Compiler::addInput(const std::string &name)
 {
@@ -615,6 +635,12 @@ void Compiler::createStop()
     impl->builder->createStop();
 }
 
+/*! Creates a call to the procedure with the given prototype. */
+void Compiler::createProcedureCall(BlockPrototype *prototype, const libscratchcpp::Compiler::Args &args)
+{
+    impl->builder->createProcedureCall(prototype, args);
+}
+
 /*! Convenience method which returns the field with the given name. */
 Input *Compiler::input(const std::string &name) const
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index e19d2ce6..ceb2d115 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -45,11 +45,10 @@ class CompilerTest : public testing::Test
             m_testVar.reset();
         }
 
-        void compile(Compiler *compiler, std::shared_ptr<Block> block)
+        void compile(Compiler *compiler, std::shared_ptr<Block> block, BlockPrototype *procedurePrototype = nullptr)
         {
             ASSERT_EQ(compiler->block(), nullptr);
-            // TODO: Test procedures
-            EXPECT_CALL(m_builderFactory, create(m_ctx.get(), nullptr)).WillOnce(Return(m_builder));
+            EXPECT_CALL(m_builderFactory, create(m_ctx.get(), procedurePrototype)).WillOnce(Return(m_builder));
             EXPECT_CALL(*m_builder, finalize()).WillOnce(Return(m_code));
             ASSERT_EQ(compiler->compile(block), m_code);
             ASSERT_EQ(compiler->block(), nullptr);
@@ -338,6 +337,25 @@ TEST_F(CompilerTest, AddListContains)
     compile(m_compiler.get(), block);
 }
 
+TEST_F(CompilerTest, AddProcedureArgument)
+{
+
+    auto block = std::make_shared<Block>("a", "");
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue ret(Compiler::StaticType::Unknown);
+
+        EXPECT_CALL(*m_builder, addProcedureArgument("arg 1")).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->addProcedureArgument("arg 1"), &ret);
+
+        EXPECT_CALL(*m_builder, addProcedureArgument("arg 2")).WillOnce(Return(nullptr));
+        EXPECT_EQ(compiler->addProcedureArgument("arg 2"), nullptr);
+
+        return nullptr;
+    });
+
+    compile(m_compiler.get(), block);
+}
+
 TEST_F(CompilerTest, AddInput)
 {
 
@@ -1572,6 +1590,25 @@ TEST_F(CompilerTest, CreateStop)
     compile(m_compiler.get(), block);
 }
 
+TEST_F(CompilerTest, CreateProcedureCall)
+{
+
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        BlockPrototype prototype;
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        Compiler::Args args = { &arg1, &arg2 };
+
+        EXPECT_CALL(*m_builder, createProcedureCall(&prototype, args));
+        compiler->createProcedureCall(&prototype, args);
+        return nullptr;
+    });
+
+    compile(m_compiler.get(), block);
+}
+
 TEST_F(CompilerTest, Input)
 {
 
@@ -1660,3 +1697,30 @@ TEST_F(CompilerTest, UnsupportedBlocks)
 
     ASSERT_EQ(m_compiler->unsupportedBlocks(), std::unordered_set<std::string>({ "block1", "block2", "value_block1", "value_block3", "value_block5", "block4" }));
 }
+
+TEST_F(CompilerTest, Procedure)
+{
+    {
+        auto block = std::make_shared<Block>("", "");
+        auto customBlock = std::make_shared<Block>("", "");
+        customBlock->mutationPrototype()->setProcCode("");
+
+        auto input = std::make_shared<Input>("custom_block", Input::Type::ObscuredShadow);
+        input->setValueBlock(customBlock);
+        block->addInput(input);
+
+        compile(m_compiler.get(), block, nullptr);
+    }
+
+    {
+        auto block = std::make_shared<Block>("", "");
+        auto customBlock = std::make_shared<Block>("", "");
+        customBlock->mutationPrototype()->setProcCode("test");
+
+        auto input = std::make_shared<Input>("custom_block", Input::Type::ObscuredShadow);
+        input->setValueBlock(customBlock);
+        block->addInput(input);
+
+        compile(m_compiler.get(), block, customBlock->mutationPrototype());
+    }
+}

From 793c5e9351965bd3bf9fd21975ab4eda89565723 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 1 Jan 2025 23:21:10 +0100
Subject: [PATCH 205/259] LLVMCodeBuilder: Add test for invalid procedure args

---
 test/dev/llvm/llvmcodebuilder_test.cpp | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 675f4440..ec0aa414 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -3976,6 +3976,9 @@ TEST_F(LLVMCodeBuilderTest, Procedures)
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
     m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
 
+    v = m_builder->addProcedureArgument("invalid");
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
     m_builder->finalize();
 
     // Procedure 2
@@ -3996,12 +3999,16 @@ TEST_F(LLVMCodeBuilderTest, Procedures)
     m_builder->createProcedureCall(&prototype1, { m_builder->addConstValue("test"), m_builder->addConstValue(true), m_builder->addConstValue(false) });
     m_builder->createProcedureCall(&prototype2, {});
 
+    v = m_builder->addProcedureArgument("test");
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
     std::string expected1 = "no_args\n";
 
     std::string expected2 =
         "test\n"
         "true\n"
         "false\n"
+        "0\n"
         "0\n";
 
     std::string expected3 =
@@ -4011,12 +4018,15 @@ TEST_F(LLVMCodeBuilderTest, Procedures)
         "false\n"
         "true\n"
         "1\n"
+        "0\n"
         "no_args\n"
         "no_args\n"
         "-652.3\n"
         "false\n"
         "true\n"
-        "1\n";
+        "1\n"
+        "0\n"
+        "0\n";
 
     auto code = m_builder->finalize();
     Script script(&sprite, nullptr, nullptr);

From 24b98b2dc08f09e246570bb48d5c8ebf064ade7c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 2 Jan 2025 19:21:28 +0100
Subject: [PATCH 206/259] Implement procedures_definition

---
 src/dev/blocks/customblocks.cpp        |  4 ++++
 test/dev/blocks/custom_blocks_test.cpp | 27 +++++++++++++++++++++++++-
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/src/dev/blocks/customblocks.cpp b/src/dev/blocks/customblocks.cpp
index 4a1657b3..c8b195c5 100644
--- a/src/dev/blocks/customblocks.cpp
+++ b/src/dev/blocks/customblocks.cpp
@@ -1,5 +1,8 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/dev/compiler.h>
+
 #include "customblocks.h"
 
 using namespace libscratchcpp;
@@ -16,4 +19,5 @@ std::string CustomBlocks::description() const
 
 void CustomBlocks::registerBlocks(IEngine *engine)
 {
+    engine->addCompileFunction(this, "procedures_definition", [](Compiler *) -> CompilerValue * { return nullptr; });
 }
diff --git a/test/dev/blocks/custom_blocks_test.cpp b/test/dev/blocks/custom_blocks_test.cpp
index a926f7db..97c344f5 100644
--- a/test/dev/blocks/custom_blocks_test.cpp
+++ b/test/dev/blocks/custom_blocks_test.cpp
@@ -1,15 +1,40 @@
+#include <scratchcpp/project.h>
+#include <scratchcpp/dev/test/scriptbuilder.h>
+#include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/sprite.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "dev/blocks/customblocks.h"
 
 using namespace libscratchcpp;
+using namespace libscratchcpp::test;
 
 class CustomBlocksTest : public testing::Test
 {
     public:
-        void SetUp() override { m_extension = std::make_unique<CustomBlocks>(); }
+        void SetUp() override
+        {
+            m_extension = std::make_unique<CustomBlocks>();
+            m_engine = m_project.engine().get();
+            m_extension->registerBlocks(m_engine);
+        }
 
         std::unique_ptr<IExtension> m_extension;
+        Project m_project;
+        IEngine *m_engine = nullptr;
         EngineMock m_engineMock;
 };
+
+TEST_F(CustomBlocksTest, Definition)
+{
+    auto target = std::make_shared<Sprite>();
+    ScriptBuilder builder(m_extension.get(), m_engine, target);
+
+    builder.addBlock("procedures_definition");
+    auto block = builder.currentBlock();
+
+    Compiler compiler(&m_engineMock, target.get());
+    compiler.compile(block);
+    ASSERT_TRUE(compiler.unsupportedBlocks().empty());
+}

From b6ae58fd20e64390921bb0bb3f2cf6a9f444c2a3 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 3 Jan 2025 01:16:25 +0100
Subject: [PATCH 207/259] ScriptBuilder: Add support for building multiple
 scripts

---
 include/scratchcpp/dev/test/scriptbuilder.h |  11 +-
 src/dev/test/scriptbuilder.cpp              | 114 ++++++++++++++------
 test/dev/test_api/scriptbuilder_test.cpp    |  38 ++++++-
 test/dev/test_api/testextension.cpp         |   7 ++
 test/dev/test_api/testextension.h           |   1 +
 5 files changed, 135 insertions(+), 36 deletions(-)

diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h
index e2045a2e..a01db6b8 100644
--- a/include/scratchcpp/dev/test/scriptbuilder.h
+++ b/include/scratchcpp/dev/test/scriptbuilder.h
@@ -25,11 +25,12 @@ class ScriptBuilderPrivate;
 class LIBSCRATCHCPP_EXPORT ScriptBuilder
 {
     public:
-        ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target);
+        ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target, bool createHatBlock = true);
         ScriptBuilder(const ScriptBuilder &) = delete;
 
         ~ScriptBuilder();
 
+        void addBlock(std::shared_ptr<Block> block);
         void addBlock(const std::string &opcode);
         void captureBlockReturnValue();
 
@@ -53,9 +54,15 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder
 
         List *capturedValues() const;
 
+        static void buildMultiple(const std::vector<ScriptBuilder *> &builders);
+
     private:
-        void addBlock(std::shared_ptr<Block> block);
+        void addBlockToList(std::shared_ptr<Block> block);
         void build(std::shared_ptr<Target> target);
+        std::string nextId();
+
+        static void addBlocksToTarget(Target *target, const std::vector<std::shared_ptr<Block>> &blocks);
+        static void addTargetToEngine(IEngine *engine, std::shared_ptr<Target> target);
 
         spimpl::unique_impl_ptr<ScriptBuilderPrivate> impl;
 };
diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp
index 641b3d0e..363aed7e 100644
--- a/src/dev/test/scriptbuilder.cpp
+++ b/src/dev/test/scriptbuilder.cpp
@@ -16,32 +16,31 @@
 using namespace libscratchcpp;
 using namespace libscratchcpp::test;
 
-static std::unordered_map<IEngine *, std::shared_ptr<List>> captureLists;
+// TODO: Add support for return values captures when building multiple scripts
+static std::unordered_map<const ScriptBuilder *, std::shared_ptr<List>> captureLists;
+static ScriptBuilder *currentScriptBuilder = nullptr;
 
 /*! Constructs ScriptBuilder. */
-ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target) :
+ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target, bool createHatBlock) :
     impl(spimpl::make_unique_impl<ScriptBuilderPrivate>(engine, target))
 {
     // Create capture list
-    if (captureLists.find(engine) != captureLists.cend()) {
-        std::cerr << "error: only one ScriptBuilder can be created for each engine" << std::endl;
-        return;
-    }
-
-    captureLists[engine] = std::make_shared<List>("", "");
+    captureLists[this] = std::make_shared<List>("", "");
 
     // Add start hat block
-    auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "script_builder_init");
-    engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
-        compiler->engine()->addGreenFlagScript(compiler->block());
-        return nullptr;
-    });
-    addBlock(block);
+    if (createHatBlock) {
+        auto block = std::make_shared<Block>(nextId(), "script_builder_init");
+        engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
+            compiler->engine()->addGreenFlagScript(compiler->block());
+            return nullptr;
+        });
+        addBlockToList(block);
+    }
 
     // Add compile function for return value capture block
     engine->addCompileFunction(extension, "script_builder_capture", [](Compiler *compiler) -> CompilerValue * {
         CompilerValue *input = compiler->addInput("VALUE");
-        compiler->createListAppend(captureLists[compiler->engine()].get(), input);
+        compiler->createListAppend(captureLists[currentScriptBuilder].get(), input);
         return nullptr;
     });
 }
@@ -49,14 +48,21 @@ ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared
 /*! Destroys ScriptBuilder. */
 ScriptBuilder::~ScriptBuilder()
 {
-    captureLists.erase(impl->engine);
+    captureLists.erase(this);
+}
+
+/*! Adds the given block to the script. */
+void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
+{
+    block->setId(nextId());
+    impl->lastBlock = block;
+    addBlockToList(block);
 }
 
 /*! Adds a block with the given opcode to the script. */
 void ScriptBuilder::addBlock(const std::string &opcode)
 {
-    impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), opcode);
-    addBlock(impl->lastBlock);
+    addBlock(std::make_shared<Block>("", opcode));
 }
 
 /*! Captures the return value of the created reporter block. It can be retrieved using capturedValues() later. */
@@ -101,7 +107,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Bl
     block->setParent(impl->lastBlock);
 
     while (block) {
-        block->setId(std::to_string(impl->blockId++));
+        block->setId(nextId());
         impl->inputBlocks.push_back(block);
 
         auto parent = block->parent();
@@ -128,7 +134,7 @@ void ScriptBuilder::addNullObscuredInput(const std::string &name)
         return;
 
     auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
-    auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "");
+    auto block = std::make_shared<Block>(nextId(), "");
     block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
     input->setValueBlock(block);
     impl->inputBlocks.push_back(block);
@@ -144,7 +150,7 @@ void ScriptBuilder::addDropdownInput(const std::string &name, const std::string
     auto input = std::make_shared<Input>(name, Input::Type::Shadow);
     impl->lastBlock->addInput(input);
 
-    auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), impl->lastBlock->opcode() + "_menu");
+    auto menu = std::make_shared<Block>(nextId(), impl->lastBlock->opcode() + "_menu");
     menu->setShadow(true);
     impl->inputBlocks.push_back(menu);
     input->setValueBlock(menu);
@@ -170,7 +176,7 @@ void ScriptBuilder::addEntityInput(const std::string &name, const std::string &e
         return;
 
     if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
-        entity->setId(std::to_string(impl->blockId++));
+        entity->setId(nextId());
         impl->entities.push_back(entity);
     }
 
@@ -188,7 +194,7 @@ void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptr<Enti
         return;
 
     if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
-        entity->setId(std::to_string(impl->blockId++));
+        entity->setId(nextId());
         impl->entities.push_back(entity);
     }
 
@@ -218,6 +224,10 @@ std::shared_ptr<Block> ScriptBuilder::currentBlock()
             target->addList(list);
 
         build(target);
+
+        std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
+        targets.erase(std::remove(targets.begin(), targets.end(), target), targets.end());
+        impl->engine->setTargets(targets);
     }
 
     return impl->lastBlock;
@@ -256,10 +266,31 @@ void ScriptBuilder::run()
 /*! Returns the list of captured block return values. */
 List *ScriptBuilder::capturedValues() const
 {
-    return captureLists[impl->engine].get();
+    return captureLists[this].get();
 }
 
-void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
+/*!
+ * Builds multiple scripts using the given script builders.
+ * \note Using run() on any of the script builders will result in all scripts without a custom hat block being called. Use this only with a single when flag clicked block.
+ * \note Return value capturing is not supported when building multiple scripts.
+ */
+void ScriptBuilder::buildMultiple(const std::vector<ScriptBuilder *> &builders)
+{
+    std::unordered_set<IEngine *> engines;
+
+    for (ScriptBuilder *builder : builders) {
+        auto target = builder->impl->target;
+        addBlocksToTarget(target.get(), builder->impl->blocks);
+        addBlocksToTarget(target.get(), builder->impl->inputBlocks);
+        addTargetToEngine(builder->impl->engine, target);
+        engines.insert(builder->impl->engine);
+    }
+
+    for (IEngine *engine : engines)
+        engine->compile();
+}
+
+void ScriptBuilder::addBlockToList(std::shared_ptr<Block> block)
 {
     if (!impl->blocks.empty()) {
         auto lastBlock = impl->blocks.back();
@@ -272,20 +303,37 @@ void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
 
 void ScriptBuilder::build(std::shared_ptr<Target> target)
 {
-    if (target->blocks().empty()) {
-        for (auto block : impl->blocks)
-            target->addBlock(block);
+    currentScriptBuilder = this;
+
+    addBlocksToTarget(target.get(), impl->blocks);
+    addBlocksToTarget(target.get(), impl->inputBlocks);
+    addTargetToEngine(impl->engine, target);
+
+    impl->engine->compile();
+    currentScriptBuilder = nullptr;
+}
+
+std::string ScriptBuilder::nextId()
+{
+    return std::to_string((uintptr_t)this) + '.' + std::to_string(impl->blockId++);
+}
+
+void ScriptBuilder::addBlocksToTarget(Target *target, const std::vector<std::shared_ptr<Block>> &blocks)
+{
+    auto targetBlocks = target->blocks();
 
-        for (auto block : impl->inputBlocks)
+    for (auto block : blocks) {
+        if (std::find(targetBlocks.begin(), targetBlocks.end(), block) == targetBlocks.end())
             target->addBlock(block);
     }
+}
 
-    std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
+void ScriptBuilder::addTargetToEngine(IEngine *engine, std::shared_ptr<Target> target)
+{
+    std::vector<std::shared_ptr<Target>> targets = engine->targets();
 
     if (std::find(targets.begin(), targets.end(), target) == targets.end()) {
         targets.push_back(target);
-        impl->engine->setTargets({ target });
+        engine->setTargets(targets);
     }
-
-    impl->engine->compile();
 }
diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp
index 2239832a..1182b18e 100644
--- a/test/dev/test_api/scriptbuilder_test.cpp
+++ b/test/dev/test_api/scriptbuilder_test.cpp
@@ -1,6 +1,7 @@
 #include <scratchcpp/dev/test/scriptbuilder.h>
 #include <scratchcpp/project.h>
 #include <scratchcpp/sprite.h>
+#include <scratchcpp/stage.h>
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/value.h>
 #include <scratchcpp/block.h>
@@ -43,11 +44,18 @@ TEST_F(ScriptBuilderTest, AddBlock)
     ASSERT_EQ(block->opcode(), "test_simple");
     ASSERT_TRUE(block->compileFunction());
 
+    block = std::make_shared<Block>("", "test_simple");
+    m_builder->addBlock(block);
+    block = m_builder->currentBlock();
+    ASSERT_TRUE(block);
+    ASSERT_EQ(block->opcode(), "test_simple");
+    ASSERT_TRUE(block->compileFunction());
+
     m_builder->build();
 
     testing::internal::CaptureStdout();
     m_builder->run();
-    ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\ntest\n");
 }
 
 TEST_F(ScriptBuilderTest, AddValueInput)
@@ -271,3 +279,31 @@ TEST_F(ScriptBuilderTest, CaptureBlockReturnValue)
     ASSERT_EQ(str, "test");
     ASSERT_EQ(value_toDouble(&values->operator[](1)), -93.4);
 }
+
+TEST_F(ScriptBuilderTest, MultipleScripts)
+{
+    ScriptBuilder builder1(&m_extension, m_engine, m_target, false);
+    builder1.addBlock("test_click_hat");
+    builder1.addBlock("test_simple");
+
+    ScriptBuilder builder2(&m_extension, m_engine, m_target);
+    builder2.addBlock("test_print");
+    builder2.addValueInput("STRING", "Hello world");
+
+    Project project;
+    IEngine *engine = project.engine().get();
+    m_extension.registerBlocks(engine);
+    auto target = std::make_shared<Stage>();
+    ScriptBuilder builder3(&m_extension, engine, target);
+    builder3.addBlock("test_simple");
+
+    ScriptBuilder::buildMultiple({ &builder1, &builder2, &builder3 });
+
+    testing::internal::CaptureStdout();
+    builder2.run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "Hello world\n");
+
+    testing::internal::CaptureStdout();
+    builder3.run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n");
+}
diff --git a/test/dev/test_api/testextension.cpp b/test/dev/test_api/testextension.cpp
index 7e6ef22d..c637e522 100644
--- a/test/dev/test_api/testextension.cpp
+++ b/test/dev/test_api/testextension.cpp
@@ -30,6 +30,7 @@ void TestExtension::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "test_teststr", &compileTestStr);
     engine->addCompileFunction(this, "test_input", &compileInput);
     engine->addCompileFunction(this, "test_substack", &compileSubstack);
+    engine->addCompileFunction(this, "test_click_hat", &compileClickHat);
 }
 
 CompilerValue *TestExtension::compileSimple(Compiler *compiler)
@@ -77,6 +78,12 @@ CompilerValue *TestExtension::compileSubstack(Compiler *compiler)
     return nullptr;
 }
 
+CompilerValue *TestExtension::compileClickHat(Compiler *compiler)
+{
+    compiler->engine()->addTargetClickScript(compiler->block());
+    return nullptr;
+}
+
 extern "C" void test_simple()
 {
     std::cout << "test" << std::endl;
diff --git a/test/dev/test_api/testextension.h b/test/dev/test_api/testextension.h
index 6c88ad25..e69bbcdd 100644
--- a/test/dev/test_api/testextension.h
+++ b/test/dev/test_api/testextension.h
@@ -21,6 +21,7 @@ class TestExtension : public IExtension
         static CompilerValue *compileTestStr(Compiler *compiler);
         static CompilerValue *compileInput(Compiler *compiler);
         static CompilerValue *compileSubstack(Compiler *compiler);
+        static CompilerValue *compileClickHat(Compiler *compiler);
 };
 
 } // namespace libscratchcpp

From 8e32dea74823fe5f062467b68d48f0d895b7b72b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 3 Jan 2025 01:18:19 +0100
Subject: [PATCH 208/259] Implement remaining procedure blocks

---
 src/dev/blocks/customblocks.cpp        | 32 +++++++++++++
 src/dev/blocks/customblocks.h          |  4 ++
 test/dev/blocks/CMakeLists.txt         |  1 +
 test/dev/blocks/custom_blocks_test.cpp | 63 ++++++++++++++++++++++++++
 4 files changed, 100 insertions(+)

diff --git a/src/dev/blocks/customblocks.cpp b/src/dev/blocks/customblocks.cpp
index c8b195c5..e78048bc 100644
--- a/src/dev/blocks/customblocks.cpp
+++ b/src/dev/blocks/customblocks.cpp
@@ -2,6 +2,9 @@
 
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/dev/compilerconstant.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/field.h>
 
 #include "customblocks.h"
 
@@ -20,4 +23,33 @@ std::string CustomBlocks::description() const
 void CustomBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "procedures_definition", [](Compiler *) -> CompilerValue * { return nullptr; });
+    engine->addCompileFunction(this, "procedures_call", &compileCall);
+    engine->addCompileFunction(this, "argument_reporter_boolean", &compileArgument);
+    engine->addCompileFunction(this, "argument_reporter_string_number", &compileArgument);
+}
+
+CompilerValue *CustomBlocks::compileCall(Compiler *compiler)
+{
+    auto block = compiler->block();
+    auto prototype = block->mutationPrototype();
+    const std::vector<std::string> &procedureArgs = prototype->argumentIds();
+    Compiler::Args args;
+
+    for (size_t i = 0; i < procedureArgs.size(); i++) {
+        auto index = block->findInput(procedureArgs[i]);
+
+        if (index == -1)
+            args.push_back(compiler->addConstValue(Value()));
+        else
+            args.push_back(compiler->addInput(block->inputAt(index).get()));
+    }
+
+    compiler->createProcedureCall(compiler->block()->mutationPrototype(), args);
+    return nullptr;
+}
+
+CompilerValue *CustomBlocks::compileArgument(Compiler *compiler)
+{
+    const std::string &argName = compiler->field("VALUE")->value().toString();
+    return compiler->addProcedureArgument(argName);
 }
diff --git a/src/dev/blocks/customblocks.h b/src/dev/blocks/customblocks.h
index 433afa36..55572ba9 100644
--- a/src/dev/blocks/customblocks.h
+++ b/src/dev/blocks/customblocks.h
@@ -14,6 +14,10 @@ class CustomBlocks : public IExtension
         std::string description() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+    private:
+        static CompilerValue *compileCall(Compiler *compiler);
+        static CompilerValue *compileArgument(Compiler *compiler);
 };
 
 } // namespace libscratchcpp
diff --git a/test/dev/blocks/CMakeLists.txt b/test/dev/blocks/CMakeLists.txt
index 5239c047..e8694da9 100644
--- a/test/dev/blocks/CMakeLists.txt
+++ b/test/dev/blocks/CMakeLists.txt
@@ -188,6 +188,7 @@ if (LIBSCRATCHCPP_ENABLE_CUSTOM_BLOCKS)
       GTest::gmock_main
       scratchcpp
       scratchcpp_mocks
+      block_test_deps
     )
 
     gtest_discover_tests(custom_blocks_test)
diff --git a/test/dev/blocks/custom_blocks_test.cpp b/test/dev/blocks/custom_blocks_test.cpp
index 97c344f5..515756f5 100644
--- a/test/dev/blocks/custom_blocks_test.cpp
+++ b/test/dev/blocks/custom_blocks_test.cpp
@@ -1,11 +1,17 @@
 #include <scratchcpp/project.h>
 #include <scratchcpp/dev/test/scriptbuilder.h>
 #include <scratchcpp/dev/compiler.h>
+#include <scratchcpp/script.h>
+#include <scratchcpp/thread.h>
 #include <scratchcpp/sprite.h>
+#include <scratchcpp/block.h>
+#include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "dev/blocks/customblocks.h"
+#include "util.h"
 
 using namespace libscratchcpp;
 using namespace libscratchcpp::test;
@@ -18,6 +24,7 @@ class CustomBlocksTest : public testing::Test
             m_extension = std::make_unique<CustomBlocks>();
             m_engine = m_project.engine().get();
             m_extension->registerBlocks(m_engine);
+            registerBlocks(m_engine, m_extension.get());
         }
 
         std::unique_ptr<IExtension> m_extension;
@@ -38,3 +45,59 @@ TEST_F(CustomBlocksTest, Definition)
     compiler.compile(block);
     ASSERT_TRUE(compiler.unsupportedBlocks().empty());
 }
+
+TEST_F(CustomBlocksTest, CallWithArguments)
+{
+    const std::string procCode = "procedure %s %b";
+    std::vector<std::string> argumentIds = { "a", "b" };
+    std::vector<std::string> argumentNames = { "string or number", "boolean" };
+    auto target = std::make_shared<Sprite>();
+
+    // Create definition
+    ScriptBuilder builder1(m_extension.get(), m_engine, target, false);
+    builder1.addBlock("procedures_prototype");
+    auto prototypeBlock = builder1.takeBlock();
+    BlockPrototype *prototype = prototypeBlock->mutationPrototype();
+    prototype->setProcCode(procCode);
+    prototype->setArgumentIds(argumentIds);
+    prototype->setArgumentNames(argumentNames);
+
+    builder1.addBlock("procedures_definition");
+    builder1.addObscuredInput("custom_block", prototypeBlock);
+    builder1.currentBlock();
+
+    // Print first arg
+    builder1.addBlock("argument_reporter_string_number");
+    builder1.addDropdownField("VALUE", "string or number");
+    auto argBlock = builder1.takeBlock();
+
+    builder1.addBlock("test_print");
+    builder1.addObscuredInput("STRING", argBlock);
+
+    // Print second arg
+    builder1.addBlock("argument_reporter_boolean");
+    builder1.addDropdownField("VALUE", "boolean");
+    argBlock = builder1.takeBlock();
+
+    builder1.addBlock("test_print");
+    builder1.addObscuredInput("STRING", argBlock);
+
+    builder1.build();
+
+    // Call the procedure
+    ScriptBuilder builder2(m_extension.get(), m_engine, target);
+    auto block = std::make_shared<Block>("", "procedures_call");
+    prototype = block->mutationPrototype();
+    prototype->setProcCode(procCode);
+    prototype->setArgumentIds(argumentIds);
+    prototype->setArgumentNames(argumentNames);
+    builder2.addBlock(block);
+    builder2.addValueInput("a", "Hello world");
+    builder2.addValueInput("b", true);
+
+    ScriptBuilder::buildMultiple({ &builder1, &builder2 });
+
+    testing::internal::CaptureStdout();
+    builder2.run();
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "Hello world\ntrue\n");
+}

From ca21398cb5b53ce547ef186e1245c9b238fda76e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 3 Jan 2025 10:59:45 +0100
Subject: [PATCH 209/259] Add preoptimize() method to CompilerContext

---
 include/scratchcpp/dev/compilercontext.h             | 6 ++++++
 src/dev/engine/internal/llvm/llvmcompilercontext.cpp | 5 +++++
 src/dev/engine/internal/llvm/llvmcompilercontext.h   | 2 ++
 3 files changed, 13 insertions(+)

diff --git a/include/scratchcpp/dev/compilercontext.h b/include/scratchcpp/dev/compilercontext.h
index 2a4c2420..8038c5fe 100644
--- a/include/scratchcpp/dev/compilercontext.h
+++ b/include/scratchcpp/dev/compilercontext.h
@@ -23,6 +23,12 @@ class LIBSCRATCHCPP_EXPORT CompilerContext
         IEngine *engine() const;
         Target *target() const;
 
+        /*!
+         * Optimizes compiled scripts ahead of time.
+         * \see Compiler#preoptimize()
+         */
+        virtual void preoptimize() { }
+
     private:
         spimpl::unique_impl_ptr<CompilerContextPrivate> impl;
 };
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index ccefb872..9fb3655f 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -24,6 +24,11 @@ LLVMCompilerContext::LLVMCompilerContext(IEngine *engine, Target *target) :
     }
 }
 
+void LLVMCompilerContext::preoptimize()
+{
+    initJit();
+}
+
 llvm::LLVMContext *LLVMCompilerContext::llvmCtx()
 {
     return m_llvmCtxPtr;
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.h b/src/dev/engine/internal/llvm/llvmcompilercontext.h
index f549ff46..7744c04d 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.h
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.h
@@ -20,6 +20,8 @@ class LLVMCompilerContext : public CompilerContext
     public:
         LLVMCompilerContext(IEngine *engine, Target *target);
 
+        void preoptimize() override;
+
         llvm::LLVMContext *llvmCtx();
         llvm::Module *module();
 

From a40cee5e9e85717c2cb6b11a7489fc0445de835f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 3 Jan 2025 11:00:23 +0100
Subject: [PATCH 210/259] Compiler: Add preoptimize() method

---
 include/scratchcpp/dev/compiler.h   |  1 +
 src/dev/engine/compiler.cpp         |  9 +++++++++
 test/dev/compiler/compiler_test.cpp | 12 ++++++++++++
 test/mocks/compilercontextmock.h    | 17 +++++++++++++++++
 4 files changed, 39 insertions(+)
 create mode 100644 test/mocks/compilercontextmock.h

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index b362afaa..203b4f0f 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -50,6 +50,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         std::shared_ptr<Block> block() const;
 
         std::shared_ptr<ExecutableCode> compile(std::shared_ptr<Block> startBlock);
+        void preoptimize();
 
         CompilerValue *addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
         CompilerValue *addTargetFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {});
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 2ce143e8..730679e9 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -106,6 +106,15 @@ std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBl
     return impl->builder->finalize();
 }
 
+/*!
+ * Optimizes all compiled scripts before they're called for the first time.
+ * \note Call this only after compiling all scripts.
+ */
+void Compiler::preoptimize()
+{
+    impl->ctx->preoptimize();
+}
+
 /*!
  * Adds a call to the given function.\n
  * For example: extern "C" bool some_block(double arg1, const char *arg2)
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index ceb2d115..e3c017f6 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -14,6 +14,7 @@
 #include <codebuilderfactorymock.h>
 #include <codebuildermock.h>
 #include <executablecodemock.h>
+#include <compilercontextmock.h>
 
 #include "../common.h"
 
@@ -1724,3 +1725,14 @@ TEST_F(CompilerTest, Procedure)
         compile(m_compiler.get(), block, customBlock->mutationPrototype());
     }
 }
+
+TEST_F(CompilerTest, Preoptimize)
+{
+    auto ctx = std::make_shared<CompilerContextMock>(&m_engine, &m_target);
+
+    EXPECT_CALL(m_builderFactory, createCtx(&m_engine, &m_target)).WillOnce(Return(ctx));
+    Compiler compiler(&m_engine, &m_target);
+
+    EXPECT_CALL(*ctx, preoptimize());
+    compiler.preoptimize();
+}
diff --git a/test/mocks/compilercontextmock.h b/test/mocks/compilercontextmock.h
new file mode 100644
index 00000000..f20c97f1
--- /dev/null
+++ b/test/mocks/compilercontextmock.h
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <scratchcpp/dev/compilercontext.h>
+#include <gmock/gmock.h>
+
+using namespace libscratchcpp;
+
+class CompilerContextMock : public CompilerContext
+{
+    public:
+        CompilerContextMock(IEngine *engine, Target *target) :
+            CompilerContext(engine, target)
+        {
+        }
+
+        MOCK_METHOD(void, preoptimize, (), (override));
+};

From 78409acd8f73160db3963b96e4f3a157d97fcc55 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 3 Jan 2025 11:01:22 +0100
Subject: [PATCH 211/259] Engine: Preoptimize compiled scripts

---
 src/engine/internal/engine.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index ee9708a8..c0bd3173 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -327,6 +327,12 @@ void Engine::compile()
 
         for (const std::string &opcode : unsupportedBlocks)
             m_unsupportedBlocks.insert(opcode);
+
+#ifdef USE_LLVM
+        // Preoptimize to avoid lag when starting scripts for the first time
+        std::cout << "Optimizing target " << target->name() << "..." << std::endl;
+        compiler.preoptimize();
+#endif
     }
 
     // Compile monitor blocks to bytecode

From 64695838efa236b78212d731caa5846caa4bbf2d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 16:21:14 +0100
Subject: [PATCH 212/259] Disable LLVM assertions in release mode

---
 build/HunterPackages.cmake | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/build/HunterPackages.cmake b/build/HunterPackages.cmake
index 9dc8f703..7bf566f3 100644
--- a/build/HunterPackages.cmake
+++ b/build/HunterPackages.cmake
@@ -11,7 +11,6 @@ set(HUNTER_SHA1 "4A018750743AC656A859C99C655723EAF68EE038")
 set(HUNTER_LLVM_VERSION 19.1.0)
 set(HUNTER_LLVM_CMAKE_ARGS
     LLVM_ENABLE_CRASH_OVERRIDES=OFF
-    LLVM_ENABLE_ASSERTIONS=ON
     LLVM_ENABLE_ZLIB=OFF
     LLVM_ENABLE_RTTI=ON
     LLVM_BUILD_EXAMPLES=OFF
@@ -21,6 +20,12 @@ set(HUNTER_LLVM_CMAKE_ARGS
     LLVM_TARGETS_TO_BUILD=host
 )
 
+if(CMAKE_BUILD_TYPE EQUAL "Debug")
+    set(HUNTER_LLVM_CMAKE_ARGS ${HUNTER_LLVM_CMAKE_ARGS} LLVM_ENABLE_ASSERTIONS=ON)
+else()
+    set(HUNTER_LLVM_CMAKE_ARGS ${HUNTER_LLVM_CMAKE_ARGS} LLVM_ENABLE_ASSERTIONS=OFF)
+endif()
+
 set(HUNTER_PACKAGES LLVM)
 
 include(FetchContent)

From 10538cd5cc6a4c4d17ae2202295a8e9aacac0551 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 16:27:14 +0100
Subject: [PATCH 213/259] Reduce LLVM optimization time

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 11 ++++++++--
 .../internal/llvm/llvmcompilercontext.cpp     | 22 ++++++++++++++++++-
 2 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index c62322cb..6e5c457a 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -76,9 +76,14 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     llvm::Value *targetLists = func->getArg(3);
     llvm::Value *warpArg = nullptr;
 
-    if (m_procedurePrototype) {
-        func->addFnAttr(llvm::Attribute::AlwaysInline);
+    if (m_procedurePrototype)
         warpArg = func->getArg(4);
+
+    if (m_procedurePrototype && m_warp)
+        func->addFnAttr(llvm::Attribute::InlineHint);
+    else {
+        func->addFnAttr(llvm::Attribute::NoInline);
+        func->addFnAttr(llvm::Attribute::OptimizeNone);
     }
 
     llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", func);
@@ -1106,6 +1111,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     // bool resume(void *)
     funcName = getResumeFunctionName(m_procedurePrototype);
     llvm::Function *resumeFunc = getOrCreateFunction(funcName, m_resumeFuncType);
+    resumeFunc->addFnAttr(llvm::Attribute::NoInline);
+    resumeFunc->addFnAttr(llvm::Attribute::OptimizeNone);
 
     entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", resumeFunc);
     m_builder.SetInsertPoint(entry);
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index 9fb3655f..a8a2d787 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -57,6 +57,8 @@ void LLVMCompilerContext::initJit()
 #endif
 
     // Optimize
+    const auto &functions = m_module->getFunctionList();
+
     llvm::PassBuilder passBuilder;
     llvm::LoopAnalysisManager loopAnalysisManager;
     llvm::FunctionAnalysisManager functionAnalysisManager;
@@ -69,9 +71,27 @@ void LLVMCompilerContext::initJit()
     passBuilder.registerLoopAnalyses(loopAnalysisManager);
     passBuilder.crossRegisterProxies(loopAnalysisManager, functionAnalysisManager, cGSCCAnalysisManager, moduleAnalysisManager);
 
-    llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3);
+    // Use O0 for the entire module
+    llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O0);
+
+    // Create a FunctionPassManager for O3 optimizations
+    llvm::FunctionPassManager functionPassManager = passBuilder.buildFunctionSimplificationPipeline(llvm::OptimizationLevel::O3, llvm::ThinOrFullLTOPhase::None);
+
+    // Run the O0 pipeline for the module
     modulePassManager.run(*m_module, moduleAnalysisManager);
 
+    // Run the O3 pipeline for specific functions
+    for (llvm::Function &func : m_module->functions()) {
+        if (!func.isDeclaration()) {
+            if (!func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
+#ifndef NDEBUG
+                std::cout << "debug: optimizing function: " << functionNames.back() << std::endl;
+#endif
+                functionPassManager.run(func, functionAnalysisManager);
+            }
+        }
+    }
+
     // Init JIT compiler
     std::string name = m_module->getName().str();
     auto err = m_jit->get()->addIRModule(llvm::orc::ThreadSafeModule(std::move(m_module), std::move(m_llvmCtx)));

From 1cf582ff52218bbda99d0b8b5700d67a5ddbfb6a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 17:54:14 +0100
Subject: [PATCH 214/259] Fix LLVM JIT hangs

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp     | 2 ++
 src/dev/engine/internal/llvm/llvmcompilercontext.cpp | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 6e5c457a..fe028be9 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -82,6 +82,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     if (m_procedurePrototype && m_warp)
         func->addFnAttr(llvm::Attribute::InlineHint);
     else {
+        // NOTE: These attributes will be overriden by LLVMCompilerContext
+        // TODO: Optimize all functions, maybe it doesn't take so long
         func->addFnAttr(llvm::Attribute::NoInline);
         func->addFnAttr(llvm::Attribute::OptimizeNone);
     }
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index a8a2d787..f5948173 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -88,6 +88,10 @@ void LLVMCompilerContext::initJit()
                 std::cout << "debug: optimizing function: " << functionNames.back() << std::endl;
 #endif
                 functionPassManager.run(func, functionAnalysisManager);
+            } else if (func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
+                // Remove this attribute to avoid JIT hangs
+                // TODO: Optimize all functions, maybe it doesn't take so long
+                func.removeFnAttr(llvm::Attribute::OptimizeNone);
             }
         }
     }

From 76e5cfceca97c9b409cafe3b6b83cc46a838b1dd Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 17:55:01 +0100
Subject: [PATCH 215/259] LLVMCompilerContext: Only optimize functions with
 external linkage

---
 src/dev/engine/internal/llvm/llvmcompilercontext.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index f5948173..a1a4003d 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -83,7 +83,7 @@ void LLVMCompilerContext::initJit()
     // Run the O3 pipeline for specific functions
     for (llvm::Function &func : m_module->functions()) {
         if (!func.isDeclaration()) {
-            if (!func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
+            if (func.hasExternalLinkage() && !func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
 #ifndef NDEBUG
                 std::cout << "debug: optimizing function: " << functionNames.back() << std::endl;
 #endif

From b701089fc97790db9edb0e495248b1dce39793b8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 17:55:57 +0100
Subject: [PATCH 216/259] LLVMCompilerContext: Compile functions ahead of time

---
 src/dev/engine/internal/llvm/llvmcompilercontext.cpp | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index a1a4003d..d9eef74d 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -58,6 +58,7 @@ void LLVMCompilerContext::initJit()
 
     // Optimize
     const auto &functions = m_module->getFunctionList();
+    std::vector<std::string> functionNames;
 
     llvm::PassBuilder passBuilder;
     llvm::LoopAnalysisManager loopAnalysisManager;
@@ -83,6 +84,8 @@ void LLVMCompilerContext::initJit()
     // Run the O3 pipeline for specific functions
     for (llvm::Function &func : m_module->functions()) {
         if (!func.isDeclaration()) {
+            functionNames.push_back(func.getName().str());
+
             if (func.hasExternalLinkage() && !func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
 #ifndef NDEBUG
                 std::cout << "debug: optimizing function: " << functionNames.back() << std::endl;
@@ -104,6 +107,14 @@ void LLVMCompilerContext::initJit()
         llvm::errs() << "error: failed to add module '" << name << "' to JIT: " << toString(std::move(err)) << "\n";
         return;
     }
+
+    // Lookup functions to JIT-compile ahead of time
+    for (const std::string &name : functionNames) {
+#ifndef NDEBUG
+        std::cout << "debug: looking up function: " << name << std::endl;
+#endif
+        lookupFunction<void *>(name);
+    }
 }
 
 bool LLVMCompilerContext::jitInitialized() const

From ba66d940269b0ab73c07e083ec4cd7de8e73cdb4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 17:56:18 +0100
Subject: [PATCH 217/259] LLVMCodeBuilder: Fix script and procedure name
 conflicts

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index fe028be9..f288622b 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1666,12 +1666,12 @@ void LLVMCodeBuilder::popScopeLevel()
 
 std::string LLVMCodeBuilder::getMainFunctionName(BlockPrototype *procedurePrototype)
 {
-    return procedurePrototype ? "f." + procedurePrototype->procCode() : "f";
+    return procedurePrototype ? "proc." + procedurePrototype->procCode() : "script";
 }
 
 std::string LLVMCodeBuilder::getResumeFunctionName(BlockPrototype *procedurePrototype)
 {
-    return procedurePrototype ? "resume." + procedurePrototype->procCode() : "resume";
+    return procedurePrototype ? "resume.proc." + procedurePrototype->procCode() : "resume.script";
 }
 
 llvm::FunctionType *LLVMCodeBuilder::getMainFunctionType(BlockPrototype *procedurePrototype)

From ce875eea20ab5d5c025298b4d904be682c87af31 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 18:01:03 +0100
Subject: [PATCH 218/259] BlockPrototype: Add support for %n args

Resolves: #616
---
 src/scratch/blockprototype_p.cpp             | 1 +
 test/scratch_classes/blockprototype_test.cpp | 7 ++++---
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/scratch/blockprototype_p.cpp b/src/scratch/blockprototype_p.cpp
index 3d780502..bfbba295 100644
--- a/src/scratch/blockprototype_p.cpp
+++ b/src/scratch/blockprototype_p.cpp
@@ -27,6 +27,7 @@ void BlockPrototypePrivate::setProcCode(const std::string &newProcCode)
             arg = false;
             switch (c) {
                 case 's':
+                case 'n':
                     argumentDefaults.push_back("");
                     argumentTypes.push_back(BlockPrototype::ArgType::StringNum);
                     break;
diff --git a/test/scratch_classes/blockprototype_test.cpp b/test/scratch_classes/blockprototype_test.cpp
index af030642..e0e777d8 100644
--- a/test/scratch_classes/blockprototype_test.cpp
+++ b/test/scratch_classes/blockprototype_test.cpp
@@ -17,16 +17,17 @@ TEST(BlockPrototypeTest, Constructors)
     ASSERT_TRUE(p2.argumentDefaults().empty());
     ASSERT_FALSE(p2.warp());
 
-    BlockPrototype p3("test %s block %s %b");
-    ASSERT_EQ(p3.procCode(), "test %s block %s %b");
+    BlockPrototype p3("test %s block %s %b test %n");
+    ASSERT_EQ(p3.procCode(), "test %s block %s %b test %n");
     ASSERT_EQ(
         p3.argumentTypes(),
         std::vector<BlockPrototype::ArgType>({
             BlockPrototype::ArgType::StringNum,
             BlockPrototype::ArgType::StringNum,
             BlockPrototype::ArgType::Bool,
+            BlockPrototype::ArgType::StringNum,
         }));
-    ASSERT_EQ(p3.argumentDefaults(), std::vector<Value>({ "", "", false }));
+    ASSERT_EQ(p3.argumentDefaults(), std::vector<Value>({ "", "", false, "" }));
     ASSERT_FALSE(p3.warp());
 }
 

From c8e7938bb3e9fcd8c2280ce6edd96cc2de39342f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 4 Jan 2025 18:18:55 +0100
Subject: [PATCH 219/259] LLVMCompilerContext: Only lookup functions with
 external linkage

---
 .../internal/llvm/llvmcompilercontext.cpp     | 24 ++++++++++---------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index d9eef74d..88e57d34 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -58,7 +58,7 @@ void LLVMCompilerContext::initJit()
 
     // Optimize
     const auto &functions = m_module->getFunctionList();
-    std::vector<std::string> functionNames;
+    std::vector<std::string> lookupNames;
 
     llvm::PassBuilder passBuilder;
     llvm::LoopAnalysisManager loopAnalysisManager;
@@ -84,17 +84,19 @@ void LLVMCompilerContext::initJit()
     // Run the O3 pipeline for specific functions
     for (llvm::Function &func : m_module->functions()) {
         if (!func.isDeclaration()) {
-            functionNames.push_back(func.getName().str());
-
-            if (func.hasExternalLinkage() && !func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
+            if (func.hasExternalLinkage()) {
+                lookupNames.push_back(func.getName().str());
+
+                if (func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
+                    // Remove this attribute to avoid JIT hangs
+                    // TODO: Optimize all functions, maybe it doesn't take so long
+                    func.removeFnAttr(llvm::Attribute::OptimizeNone);
+                } else {
 #ifndef NDEBUG
-                std::cout << "debug: optimizing function: " << functionNames.back() << std::endl;
+                    std::cout << "debug: optimizing function: " << func.getName().str() << std::endl;
 #endif
-                functionPassManager.run(func, functionAnalysisManager);
-            } else if (func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
-                // Remove this attribute to avoid JIT hangs
-                // TODO: Optimize all functions, maybe it doesn't take so long
-                func.removeFnAttr(llvm::Attribute::OptimizeNone);
+                    functionPassManager.run(func, functionAnalysisManager);
+                }
             }
         }
     }
@@ -109,7 +111,7 @@ void LLVMCompilerContext::initJit()
     }
 
     // Lookup functions to JIT-compile ahead of time
-    for (const std::string &name : functionNames) {
+    for (const std::string &name : lookupNames) {
 #ifndef NDEBUG
         std::cout << "debug: looking up function: " << name << std::endl;
 #endif

From 802e71f6e157bd1639269055b235cfa508e7b39e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 7 Jan 2025 22:04:56 +0100
Subject: [PATCH 220/259] LLVMCodeBuilder: Add main function pointer member

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 117 +++++++++---------
 .../engine/internal/llvm/llvmcodebuilder.h    |   9 +-
 2 files changed, 63 insertions(+), 63 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index f288622b..7e6e519e 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -63,40 +63,39 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     // Create function
     std::string funcName = getMainFunctionName(m_procedurePrototype);
     llvm::FunctionType *funcType = getMainFunctionType(m_procedurePrototype);
-    llvm::Function *func;
 
     if (m_procedurePrototype)
-        func = getOrCreateFunction(funcName, funcType);
+        m_function = getOrCreateFunction(funcName, funcType);
     else
-        func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, funcName, m_module);
+        m_function = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, funcName, m_module);
 
-    llvm::Value *executionContextPtr = func->getArg(0);
-    llvm::Value *targetPtr = func->getArg(1);
-    llvm::Value *targetVariables = func->getArg(2);
-    llvm::Value *targetLists = func->getArg(3);
+    llvm::Value *executionContextPtr = m_function->getArg(0);
+    llvm::Value *targetPtr = m_function->getArg(1);
+    llvm::Value *targetVariables = m_function->getArg(2);
+    llvm::Value *targetLists = m_function->getArg(3);
     llvm::Value *warpArg = nullptr;
 
     if (m_procedurePrototype)
-        warpArg = func->getArg(4);
+        warpArg = m_function->getArg(4);
 
     if (m_procedurePrototype && m_warp)
-        func->addFnAttr(llvm::Attribute::InlineHint);
+        m_function->addFnAttr(llvm::Attribute::InlineHint);
     else {
         // NOTE: These attributes will be overriden by LLVMCompilerContext
         // TODO: Optimize all functions, maybe it doesn't take so long
-        func->addFnAttr(llvm::Attribute::NoInline);
-        func->addFnAttr(llvm::Attribute::OptimizeNone);
+        m_function->addFnAttr(llvm::Attribute::NoInline);
+        m_function->addFnAttr(llvm::Attribute::OptimizeNone);
     }
 
-    llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", func);
-    llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", func);
+    llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", m_function);
+    llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", m_function);
     m_builder.SetInsertPoint(entry);
 
     // Init coroutine
     std::unique_ptr<LLVMCoroutine> coro;
 
     if (!m_warp)
-        coro = std::make_unique<LLVMCoroutine>(m_module, &m_builder, func);
+        coro = std::make_unique<LLVMCoroutine>(m_module, &m_builder, m_function);
 
     std::vector<LLVMIfStatement> ifStatements;
     std::vector<LLVMLoop> loops;
@@ -677,14 +676,14 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
                 llvm::Value *isAllocated = m_builder.CreateICmpUGT(allocatedSize, size);
-                llvm::BasicBlock *ifBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-                llvm::BasicBlock *elseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *ifBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                llvm::BasicBlock *elseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
                 m_builder.CreateCondBr(isAllocated, ifBlock, elseBlock);
 
                 // If there's enough space, use the allocated memory
                 m_builder.SetInsertPoint(ifBlock);
-                llvm::Value *itemPtr = getListItem(listPtr, size, func);
+                llvm::Value *itemPtr = getListItem(listPtr, size);
                 createReusedValueStore(arg.second, itemPtr, type);
                 m_builder.CreateStore(m_builder.CreateAdd(size, m_builder.getInt64(1)), listPtr.sizePtr);
                 m_builder.CreateBr(nextBlock);
@@ -748,7 +747,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
-                llvm::Value *itemPtr = getListItem(listPtr, index, func);
+                llvm::Value *itemPtr = getListItem(listPtr, index);
                 createValueStore(valueArg.second, itemPtr, type, listPtr.type);
 
                 auto &typeMap = m_scopeLists.back();
@@ -778,7 +777,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
-                step.functionReturnReg->value = getListItem(listPtr, index, func);
+                step.functionReturnReg->value = getListItem(listPtr, index);
                 step.functionReturnReg->setType(listPtr.type);
                 break;
             }
@@ -795,7 +794,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second, func), m_builder.getDoubleTy());
+                step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second), m_builder.getDoubleTy());
                 break;
             }
 
@@ -803,20 +802,20 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                llvm::Value *index = getListItemIndex(listPtr, arg.second, func);
+                llvm::Value *index = getListItemIndex(listPtr, arg.second);
                 step.functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true));
                 break;
             }
 
             case LLVMInstruction::Type::Yield:
                 // TODO: Do not allow use after suspend (use after free)
-                createSuspend(coro.get(), func, warpArg, targetVariables);
+                createSuspend(coro.get(), warpArg, targetVariables);
                 break;
 
             case LLVMInstruction::Type::BeginIf: {
                 LLVMIfStatement statement;
                 statement.beforeIf = m_builder.GetInsertBlock();
-                statement.body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                statement.body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 // Use last reg
                 assert(step.args.size() == 1);
@@ -847,13 +846,13 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Jump to the branch after the if statement
                 assert(!statement.afterIf);
-                statement.afterIf = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                statement.afterIf = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
                 freeScopeHeap();
                 m_builder.CreateBr(statement.afterIf);
 
                 // Create else branch
                 assert(!statement.elseBranch);
-                statement.elseBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                statement.elseBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 // Since there's an else branch, the conditional instruction should jump to it
                 m_builder.SetInsertPoint(statement.beforeIf);
@@ -871,7 +870,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // Jump to the branch after the if statement
                 if (!statement.afterIf)
-                    statement.afterIf = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                    statement.afterIf = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 m_builder.CreateBr(statement.afterIf);
 
@@ -900,9 +899,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 m_builder.CreateStore(zero, loop.index);
 
                 // Create branches
-                llvm::BasicBlock *roundBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-                loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *roundBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 // Use last reg for count
                 assert(step.args.size() == 1);
@@ -928,10 +927,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // Check index
                 m_builder.SetInsertPoint(loop.conditionBranch);
 
-                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 if (!loop.afterLoop)
-                    loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                    loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index);
                 comparison = m_builder.CreateOr(isInf, m_builder.CreateICmpULT(currentIndex, count));
@@ -958,8 +957,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMLoop &loop = loops.back();
 
                 // Create branches
-                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 // Use last reg
                 assert(step.args.size() == 1);
@@ -979,8 +978,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMLoop &loop = loops.back();
 
                 // Create branches
-                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
                 // Use last reg
                 assert(step.args.size() == 1);
@@ -998,7 +997,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::BeginLoopCondition: {
                 LLVMLoop loop;
                 loop.isRepeatLoop = false;
-                loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
                 m_builder.CreateBr(loop.conditionBranch);
                 m_builder.SetInsertPoint(loop.conditionBranch);
                 loops.push_back(loop);
@@ -1030,7 +1029,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
             case LLVMInstruction::Type::Stop: {
                 m_builder.CreateBr(endBranch);
-                llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
                 m_builder.SetInsertPoint(nextBranch);
                 break;
             }
@@ -1046,7 +1045,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 std::vector<llvm::Value *> args;
 
                 for (size_t i = 0; i < m_defaultArgCount; i++)
-                    args.push_back(func->getArg(i));
+                    args.push_back(m_function->getArg(i));
 
                 // Add warp arg
                 if (m_warp)
@@ -1065,12 +1064,12 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Value *handle = m_builder.CreateCall(resolveFunction(name, type), args);
 
                 if (!m_warp && !step.procedurePrototype->warp()) {
-                    llvm::BasicBlock *suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-                    llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+                    llvm::BasicBlock *suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                    llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
                     m_builder.CreateCondBr(m_builder.CreateIsNull(handle), nextBranch, suspendBranch);
 
                     m_builder.SetInsertPoint(suspendBranch);
-                    createSuspend(coro.get(), func, warpArg, targetVariables);
+                    createSuspend(coro.get(), warpArg, targetVariables);
                     name = getResumeFunctionName(step.procedurePrototype);
                     llvm::Value *done = m_builder.CreateCall(resolveFunction(name, m_resumeFuncType), { handle });
                     m_builder.CreateCondBr(done, nextBranch, suspendBranch);
@@ -1085,7 +1084,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
             case LLVMInstruction::Type::ProcedureArg: {
                 assert(m_procedurePrototype);
-                llvm::Value *arg = func->getArg(m_defaultArgCount + 1 + step.procedureArgIndex); // omit warp arg
+                llvm::Value *arg = m_function->getArg(m_defaultArgCount + 1 + step.procedureArgIndex); // omit warp arg
                 step.functionReturnReg->value = arg;
                 break;
             }
@@ -1107,7 +1106,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
     else
         coro->end();
 
-    verifyFunction(func);
+    verifyFunction(m_function);
 
     // Create resume function
     // bool resume(void *)
@@ -1126,7 +1125,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
     verifyFunction(resumeFunc);
 
-    return std::make_shared<LLVMExecutableCode>(m_ctx, func->getName().str(), resumeFunc->getName().str());
+    return std::make_shared<LLVMExecutableCode>(m_ctx, m_function->getName().str(), resumeFunc->getName().str());
 }
 
 CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
@@ -2029,7 +2028,7 @@ void LLVMCodeBuilder::reloadLists()
     }
 }
 
-void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func)
+void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr)
 {
     // dataPtr = dirty ? list_data(list) : dataPtr
     // dirty = false
@@ -2190,21 +2189,21 @@ void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target,
     m_builder.CreateStore(m_builder.CreateLoad(fieldType, sourceField), targetField);
 }
 
-llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func)
+llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Value *index)
 {
-    updateListDataPtr(listPtr, func);
+    updateListDataPtr(listPtr);
     return m_builder.CreateGEP(m_valueDataType, m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index);
 }
 
-llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item, llvm::Function *func)
+llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item)
 {
     llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
-    llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-    llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-    llvm::BasicBlock *cmpIfBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-    llvm::BasicBlock *cmpElseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-    llvm::BasicBlock *notFoundBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-    llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+    llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+    llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+    llvm::BasicBlock *cmpIfBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+    llvm::BasicBlock *cmpElseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+    llvm::BasicBlock *notFoundBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+    llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
     // index = 0
     llvm::Value *index = m_builder.CreateAlloca(m_builder.getInt64Ty());
@@ -2220,7 +2219,7 @@ llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMR
     m_builder.SetInsertPoint(bodyBlock);
     LLVMRegister currentItem(listPtr.type);
     currentItem.isRawValue = false;
-    currentItem.value = getListItem(listPtr, m_builder.CreateLoad(m_builder.getInt64Ty(), index), func);
+    currentItem.value = getListItem(listPtr, m_builder.CreateLoad(m_builder.getInt64Ty(), index));
     llvm::Value *cmp = createComparison(&currentItem, item, Comparison::EQ);
     m_builder.CreateCondBr(cmp, cmpIfBlock, cmpElseBlock);
 
@@ -2481,14 +2480,14 @@ llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegister *arg1, LLVMRegister
     }
 }
 
-void LLVMCodeBuilder::createSuspend(LLVMCoroutine *coro, llvm::Function *func, llvm::Value *warpArg, llvm::Value *targetVariables)
+void LLVMCodeBuilder::createSuspend(LLVMCoroutine *coro, llvm::Value *warpArg, llvm::Value *targetVariables)
 {
     if (!m_warp) {
         llvm::BasicBlock *suspendBranch, *nextBranch;
 
         if (warpArg) {
-            suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
-            nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", func);
+            suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+            nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
             m_builder.CreateCondBr(warpArg, nextBranch, suspendBranch);
             m_builder.SetInsertPoint(suspendBranch);
         }
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 85f0beba..f5bf5680 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -142,7 +142,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         void syncVariables(llvm::Value *targetVariables);
         void reloadVariables(llvm::Value *targetVariables);
         void reloadLists();
-        void updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func);
+        void updateListDataPtr(const LLVMListPtr &listPtr);
 
         LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
         LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
@@ -151,12 +151,12 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
-        llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index, llvm::Function *func);
-        llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item, llvm::Function *func);
+        llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index);
+        llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item);
         llvm::Value *createValue(LLVMRegister *reg);
         llvm::Value *createComparison(LLVMRegister *arg1, LLVMRegister *arg2, Comparison type);
 
-        void createSuspend(LLVMCoroutine *coro, llvm::Function *func, llvm::Value *warpArg, llvm::Value *targetVariables);
+        void createSuspend(LLVMCoroutine *coro, llvm::Value *warpArg, llvm::Value *targetVariables);
 
         llvm::FunctionCallee resolveFunction(const std::string name, llvm::FunctionType *type);
         llvm::FunctionCallee resolve_value_init();
@@ -205,6 +205,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::LLVMContext &m_llvmCtx;
         llvm::Module *m_module = nullptr;
         llvm::IRBuilder<> m_builder;
+        llvm::Function *m_function = nullptr;
 
         llvm::StructType *m_valueDataType = nullptr;
         llvm::FunctionType *m_resumeFuncType = nullptr;

From 62cc8acfa815fe5e76088cfc353b39c498cf4db5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 7 Jan 2025 22:06:13 +0100
Subject: [PATCH 221/259] LLVMCodeBuilder: Create allocas in entry block

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 20 ++++++++++++++-----
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 +
 2 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 7e6e519e..f78a20f5 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -562,7 +562,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                         break;
                 }
 
-                step.functionReturnReg->value = m_builder.CreateAlloca(type);
+                step.functionReturnReg->value = addAlloca(type);
                 break;
             }
 
@@ -895,7 +895,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 // index = 0
                 llvm::Constant *zero = llvm::ConstantInt::get(m_builder.getInt64Ty(), 0, true);
-                loop.index = m_builder.CreateAlloca(m_builder.getInt64Ty());
+                loop.index = addAlloca(m_builder.getInt64Ty());
                 m_builder.CreateStore(zero, loop.index);
 
                 // Create branches
@@ -1718,6 +1718,16 @@ LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg)
     return reg.get();
 }
 
+llvm::Value *LLVMCodeBuilder::addAlloca(llvm::Type *type)
+{
+    // Add an alloca to the entry block because allocas must be there (to avoid stack overflow)
+    llvm::BasicBlock *block = m_builder.GetInsertBlock();
+    m_builder.SetInsertPointPastAllocas(m_function);
+    llvm::Value *ret = m_builder.CreateAlloca(type);
+    m_builder.SetInsertPoint(block);
+    return ret;
+}
+
 void LLVMCodeBuilder::freeLater(llvm::Value *value)
 {
     assert(!m_heap.empty());
@@ -2206,7 +2216,7 @@ llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMR
     llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
 
     // index = 0
-    llvm::Value *index = m_builder.CreateAlloca(m_builder.getInt64Ty());
+    llvm::Value *index = addAlloca(m_builder.getInt64Ty());
     m_builder.CreateStore(m_builder.getInt64(0), index);
     m_builder.CreateBr(condBlock);
 
@@ -2250,7 +2260,7 @@ llvm::Value *LLVMCodeBuilder::createValue(LLVMRegister *reg)
     if (reg->isConst()) {
         // Create a constant ValueData instance and store it
         llvm::Constant *value = castConstValue(reg->constValue(), TYPE_MAP[reg->constValue().type()]);
-        llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType);
+        llvm::Value *ret = addAlloca(m_valueDataType);
 
         switch (reg->constValue().type()) {
             case ValueType::Number:
@@ -2279,7 +2289,7 @@ llvm::Value *LLVMCodeBuilder::createValue(LLVMRegister *reg)
         return ret;
     } else if (reg->isRawValue) {
         llvm::Value *value = castRawValue(reg, reg->type());
-        llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType);
+        llvm::Value *ret = addAlloca(m_valueDataType);
 
         // Store value
         llvm::Value *valueField = m_builder.CreateStructGEP(m_valueDataType, ret, 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index f5bf5680..848b4a23 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -126,6 +126,7 @@ class LLVMCodeBuilder : public ICodeBuilder
 
         LLVMRegister *addReg(std::shared_ptr<LLVMRegister> reg);
 
+        llvm::Value *addAlloca(llvm::Type *type);
         void freeLater(llvm::Value *value);
         void freeScopeHeap();
         llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType);

From a5f91f673334a0aedb261ee8cfca5a90071bf6fc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 11 Jan 2025 14:37:00 +0100
Subject: [PATCH 222/259] Refactor double to string conversion

---
 src/scratch/value_functions.cpp     | 114 +++++++++++++++++++++++-----
 src/scratch/value_functions_p.h     |  58 ++++----------
 test/scratch_classes/value_test.cpp |  78 +++++++++++++++++++
 3 files changed, 186 insertions(+), 64 deletions(-)

diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp
index d0c85537..0e157b65 100644
--- a/src/scratch/value_functions.cpp
+++ b/src/scratch/value_functions.cpp
@@ -242,16 +242,11 @@ extern "C"
     }
 
     /*! Writes the string representation of the given value to dst. */
-    void value_toString(const libscratchcpp::ValueData *v, std::string *dst)
+    void value_toString(const ValueData *v, std::string *dst)
     {
-        if (v->type == ValueType::String)
-            dst->assign(v->stringValue);
-        else if (v->type == ValueType::Number)
-            value_doubleToString(v->numberValue, dst);
-        else if (v->type == ValueType::Bool)
-            dst->assign(v->boolValue ? "true" : "false");
-        else
-            dst->clear();
+        char *str = value_toCString(v);
+        dst->assign(str);
+        free(str);
     }
 
     /*!
@@ -260,11 +255,29 @@ extern "C"
      */
     char *value_toCString(const ValueData *v)
     {
-        std::string out;
-        value_toString(v, &out);
-        char *ret = (char *)malloc((out.size() + 1) * sizeof(char));
-        strncpy(ret, out.c_str(), out.size() + 1);
-        return ret;
+        if (v->type == ValueType::String) {
+            char *ret = (char *)malloc((strlen(v->stringValue) + 1) * sizeof(char));
+            strcpy(ret, v->stringValue);
+            return ret;
+        } else if (v->type == ValueType::Number)
+            return value_doubleToCString(v->numberValue);
+        else if (v->type == ValueType::Bool) {
+            char *ret;
+
+            if (v->boolValue) {
+                ret = (char *)malloc((4 + 1) * sizeof(char));
+                strcpy(ret, "true");
+            } else {
+                ret = (char *)malloc((5 + 1) * sizeof(char));
+                strcpy(ret, "false");
+            }
+
+            return ret;
+        } else {
+            char *ret = (char *)malloc(sizeof(char));
+            ret[0] = '\0';
+            return ret;
+        }
     }
 
     /*! Writes the UTF-16 representation of the given value to dst. */
@@ -292,11 +305,74 @@ extern "C"
      */
     char *value_doubleToCString(double v)
     {
-        std::string out;
-        value_doubleToString(v, &out);
-        char *ret = (char *)malloc((out.size() + 1) * sizeof(char));
-        strncpy(ret, out.c_str(), out.size() + 1);
-        return ret;
+        if (v == 0) {
+            char *ret = (char *)malloc((1 + 1) * sizeof(char));
+            strcpy(ret, "0");
+            return ret;
+        } else if (std::isinf(v)) {
+            if (v > 0) {
+                char *ret = (char *)malloc((8 + 1) * sizeof(char));
+                strcpy(ret, "Infinity");
+                return ret;
+            } else {
+                char *ret = (char *)malloc((9 + 1) * sizeof(char));
+                strcpy(ret, "-Infinity");
+                return ret;
+            }
+        } else if (std::isnan(v)) {
+            char *ret = (char *)malloc((3 + 1) * sizeof(char));
+            strcpy(ret, "NaN");
+            return ret;
+        }
+
+        const int maxlen = 26; // should be enough for any number
+        char *buffer = (char *)malloc((maxlen + 1) * sizeof(char));
+
+        // Constants for significant digits and thresholds
+        const int significantDigits = 17 - value_intDigitCount(std::floor(std::fabs(v)));
+        constexpr double scientificThreshold = 1e21;
+        constexpr double minScientificThreshold = 1e-6;
+
+        // Use scientific notation if the number is very large or very small
+        if (std::fabs(v) >= scientificThreshold || std::fabs(v) < minScientificThreshold) {
+            int ret = snprintf(buffer, maxlen, "%.*g", significantDigits - 1, v);
+            assert(ret >= 0);
+        } else {
+            snprintf(buffer, maxlen, "%.*f", significantDigits - 1, v);
+
+            // Remove trailing zeros from the fractional part
+            char *dot = std::strchr(buffer, '.');
+
+            if (dot) {
+                char *end = buffer + std::strlen(buffer) - 1;
+                while (end > dot && *end == '0') {
+                    *end-- = '\0';
+                }
+                if (*end == '.') {
+                    *end = '\0'; // Remove trailing dot
+                }
+            }
+        }
+
+        // Remove leading zeros after e+/e-
+        for (int i = 0; i < 2; i++) {
+            const char *target = (i == 0) ? "e+" : "e-";
+            char *index = strstr(buffer, target);
+
+            if (index != nullptr) {
+                char *ptr = index + 2;
+                while (*(ptr + 1) != '\0' && *ptr == '0') {
+                    // Shift the characters left to erase '0'
+                    char *shiftPtr = ptr;
+                    do {
+                        *shiftPtr = *(shiftPtr + 1);
+                        shiftPtr++;
+                    } while (*shiftPtr != '\0');
+                }
+            }
+        }
+
+        return buffer;
     }
 
     /*!
diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h
index 01958ab5..05ec0b88 100644
--- a/src/scratch/value_functions_p.h
+++ b/src/scratch/value_functions_p.h
@@ -18,6 +18,19 @@
 namespace libscratchcpp
 {
 
+template<typename T>
+inline unsigned int value_intDigitCount(T v)
+{
+    unsigned int i = 0;
+
+    while (v >= 1) {
+        v /= 10;
+        i++;
+    }
+
+    return i;
+}
+
 template<typename T>
 inline unsigned int value_digitCount(T v)
 {
@@ -407,51 +420,6 @@ extern "C"
     }
 }
 
-inline void value_doubleToString(double v, std::string *dst)
-{
-    if (v == 0) {
-        dst->assign("0");
-        return;
-    } else if (std::isinf(v)) {
-        if (v > 0)
-            dst->assign("Infinity");
-        else
-            dst->assign("-Infinity");
-
-        return;
-    } else if (std::isnan(v)) {
-        dst->assign("NaN");
-        return;
-    }
-
-    std::stringstream stream;
-
-    if (v != 0) {
-        const int exponent = std::log10(std::abs(v));
-
-        if (exponent > 20)
-            stream << std::scientific << std::setprecision(value_digitCount(v / std::pow(10, exponent + 1)) - 1) << v;
-        else
-            stream << std::setprecision(std::max(16u, value_digitCount(v))) << v;
-    } else
-        stream << std::setprecision(std::max(16u, value_digitCount(v))) << v;
-
-    dst->assign(stream.str());
-    std::size_t index;
-
-    for (int i = 0; i < 2; i++) {
-        if (i == 0)
-            index = dst->find("e+");
-        else
-            index = dst->find("e-");
-
-        if (index != std::string::npos) {
-            while ((dst->size() >= index + 3) && ((*dst)[index + 2] == '0'))
-                dst->erase(index + 2, 1);
-        }
-    }
-}
-
 extern "C"
 {
     inline double value_floatToDouble(float v)
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 901d44ad..71e31bfd 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1401,6 +1401,30 @@ TEST(ValueTest, ToString)
     ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
     ASSERT_EQ(std::string(cStrings.back()), v.toString());
 
+    v = 59.8;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "59.8");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+
+    v = -59.8;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "-59.8");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+
+    v = 5.3;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "5.3");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+
+    v = -5.3;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "-5.3");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+
     v = 2550.625021000115;
     cStrings.push_back(value_toCString(&v.data()));
     ASSERT_EQ(v.toString(), "2550.625021000115");
@@ -1448,6 +1472,28 @@ TEST(ValueTest, ToString)
     ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
     ASSERT_EQ(std::string(cStrings.back()), v.toString());
 
+    v = 0.000001;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "0.000001");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+    v = -0.000001;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "-0.000001");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+
+    v = 0.0000001;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "1e-7");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+    v = -0.0000001;
+    cStrings.push_back(value_toCString(&v.data()));
+    ASSERT_EQ(v.toString(), "-1e-7");
+    ASSERT_EQ(utf8::utf16to8(v.toUtf16()), v.toString());
+    ASSERT_EQ(std::string(cStrings.back()), v.toString());
+
     v = false;
     cStrings.push_back(value_toCString(&v.data()));
     ASSERT_EQ(v.toString(), "false");
@@ -2846,6 +2892,22 @@ TEST(ValueTest, DoubleToCString)
     ASSERT_EQ(strcmp(ret, "-2.54"), 0);
     free(ret);
 
+    ret = value_doubleToCString(59.8);
+    ASSERT_EQ(strcmp(ret, "59.8"), 0);
+    free(ret);
+
+    ret = value_doubleToCString(-59.8);
+    ASSERT_EQ(strcmp(ret, "-59.8"), 0);
+    free(ret);
+
+    ret = value_doubleToCString(5.3);
+    ASSERT_EQ(strcmp(ret, "5.3"), 0);
+    free(ret);
+
+    ret = value_doubleToCString(-5.3);
+    ASSERT_EQ(strcmp(ret, "-5.3"), 0);
+    free(ret);
+
     ret = value_doubleToCString(2550.625021000115);
     ASSERT_EQ(strcmp(ret, "2550.625021000115"), 0);
     free(ret);
@@ -2878,6 +2940,22 @@ TEST(ValueTest, DoubleToCString)
     ASSERT_EQ(strcmp(ret, "-0.001"), 0);
     free(ret);
 
+    ret = value_doubleToCString(0.000001);
+    ASSERT_EQ(strcmp(ret, "0.000001"), 0);
+    free(ret);
+
+    ret = value_doubleToCString(-0.000001);
+    ASSERT_EQ(strcmp(ret, "-0.000001"), 0);
+    free(ret);
+
+    ret = value_doubleToCString(0.0000001);
+    ASSERT_EQ(strcmp(ret, "1e-7"), 0);
+    free(ret);
+
+    ret = value_doubleToCString(-0.0000001);
+    ASSERT_EQ(strcmp(ret, "-1e-7"), 0);
+    free(ret);
+
     ret = value_doubleToCString(std::numeric_limits<double>::infinity());
     ASSERT_EQ(strcmp(ret, "Infinity"), 0);
     free(ret);

From 6e0624ca3059b7ae0517c139d32d9063fcee98d2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 11 Jan 2025 14:37:29 +0100
Subject: [PATCH 223/259] Sprite: Remove redundant eng variable

---
 src/scratch/sprite.cpp | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index 5cf80e68..9ca3347a 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -579,12 +579,8 @@ void Sprite::setXY(double x, double y)
     } else
         impl->getFencedPosition(x, y, &impl->x, &impl->y);
 
-    if (impl->visible) {
-        IEngine *eng = engine();
-
-        if (eng)
-            eng->requestRedraw();
-    }
+    if (eng && impl->visible)
+        eng->requestRedraw();
 
     if (impl->iface)
         impl->iface->onMoved(oldX, oldY, impl->x, impl->y);

From bce615b4879d761103bd0d85dd0b8f0a660cd3fa Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 11 Jan 2025 20:53:48 +0100
Subject: [PATCH 224/259] LLVMCodeBuilder: Optimize list writing

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 78 ++++++++-----------
 .../engine/internal/llvm/llvmcodebuilder.h    |  2 +-
 2 files changed, 33 insertions(+), 47 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index f78a20f5..10520ae9 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -672,6 +672,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(arg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
+                auto &typeMap = m_scopeLists.back();
+
+                if (typeMap.find(&listPtr) == typeMap.cend()) {
+                    listPtr.type = type;
+                    typeMap[&listPtr] = listPtr.type;
+                } else if (listPtr.type != type) {
+                    listPtr.type = Compiler::StaticType::Unknown;
+                    typeMap[&listPtr] = listPtr.type;
+                }
+
                 // Check if enough space is allocated
                 llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
@@ -684,27 +694,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // If there's enough space, use the allocated memory
                 m_builder.SetInsertPoint(ifBlock);
                 llvm::Value *itemPtr = getListItem(listPtr, size);
-                createReusedValueStore(arg.second, itemPtr, type);
+                createReusedValueStore(arg.second, itemPtr, type, listPtr.type);
                 m_builder.CreateStore(m_builder.CreateAdd(size, m_builder.getInt64(1)), listPtr.sizePtr);
                 m_builder.CreateBr(nextBlock);
 
                 // Otherwise call appendEmpty()
                 m_builder.SetInsertPoint(elseBlock);
                 itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr);
-                createReusedValueStore(arg.second, itemPtr, type);
+                createReusedValueStore(arg.second, itemPtr, type, listPtr.type);
                 m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty);
                 m_builder.CreateBr(nextBlock);
 
                 m_builder.SetInsertPoint(nextBlock);
-                auto &typeMap = m_scopeLists.back();
-
-                if (typeMap.find(&listPtr) == typeMap.cend()) {
-                    listPtr.type = type;
-                    typeMap[&listPtr] = listPtr.type;
-                } else if (listPtr.type != type) {
-                    listPtr.type = Compiler::StaticType::Unknown;
-                    typeMap[&listPtr] = listPtr.type;
-                }
 
                 break;
             }
@@ -716,17 +717,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
-                // dataPtrDirty
-                llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
-                llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
-                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
-                m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpEQ(allocatedSize, size)), listPtr.dataPtrDirty);
-
-                // Insert
-                llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
-                llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
-                createReusedValueStore(valueArg.second, itemPtr, type);
-
                 auto &typeMap = m_scopeLists.back();
 
                 if (typeMap.find(&listPtr) == typeMap.cend()) {
@@ -737,6 +727,17 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     typeMap[&listPtr] = listPtr.type;
                 }
 
+                // dataPtrDirty
+                llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
+                llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+                m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpEQ(allocatedSize, size)), listPtr.dataPtrDirty);
+
+                // Insert
+                llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
+                llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
+                createReusedValueStore(valueArg.second, itemPtr, type, listPtr.type);
+
                 break;
             }
 
@@ -2153,33 +2154,18 @@ void LLVMCodeBuilder::createValueStore(LLVMRegister *reg, llvm::Value *targetPtr
     }
 }
 
-void LLVMCodeBuilder::createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType)
+void LLVMCodeBuilder::createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType)
 {
-    llvm::Value *converted = nullptr;
-
-    if (sourceType != Compiler::StaticType::Unknown)
-        converted = castValue(reg, sourceType);
-
-    switch (sourceType) {
-        case Compiler::StaticType::Number:
-            m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted });
-            break;
+    // Same as createValueStore(), but ensures that type is updated
+    createValueStore(reg, targetPtr, sourceType, targetType);
 
-        case Compiler::StaticType::Bool:
-            m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted });
-            break;
-
-        case Compiler::StaticType::String:
-            m_builder.CreateCall(resolve_value_assign_cstring(), { targetPtr, converted });
-            break;
-
-        case Compiler::StaticType::Unknown:
-            m_builder.CreateCall(resolve_value_assign_copy(), { targetPtr, reg->value });
-            break;
+    auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair<ValueType, Compiler::StaticType> &pair) { return pair.second == sourceType; });
+    const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored
 
-        default:
-            assert(false);
-            break;
+    if ((sourceType == Compiler::StaticType::Number || sourceType == Compiler::StaticType::Bool) && sourceType == targetType) {
+        // Update type when writing number to number and bool to bool
+        llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1);
+        m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typePtr);
     }
 }
 
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 848b4a23..9728b6cb 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -149,7 +149,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
 
         void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
-        void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType);
+        void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
         void createValueCopy(llvm::Value *source, llvm::Value *target);
         void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType);
         llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index);

From a3de164a43b1b4a45a81e05dcf812fc893d1e33a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 11 Jan 2025 21:23:57 +0100
Subject: [PATCH 225/259] LLVMCodeBuilder: Optimize number and string const
 comparison

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 10 ++++++++++
 test/dev/llvm/llvmcodebuilder_test.cpp           |  2 ++
 2 files changed, 12 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 10520ae9..0404f448 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -2347,6 +2347,16 @@ llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegister *arg1, LLVMRegister
             optNumberBool = 1; // operand 1 was bool
         }
 
+        // Optimize number and string constant comparison
+        // TODO: GT and LT comparison can be optimized here (e. g. by checking the string constant characters and comparing with numbers and .+-e)
+        if (type == Comparison::EQ) {
+            if (type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::String && arg2->isConst() && !arg2->constValue().isValidNumber())
+                return m_builder.getInt1(false);
+
+            if (type1 == Compiler::StaticType::String && type2 == Compiler::StaticType::Number && arg1->isConst() && !arg1->constValue().isValidNumber())
+                return m_builder.getInt1(false);
+        }
+
         if (type1 != type2 || type1 == Compiler::StaticType::Unknown || type2 == Compiler::StaticType::Unknown) {
             // If the types are different or at least one of them
             // is unknown, we must use value functions
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index ec0aa414..c8b9c916 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -983,6 +983,8 @@ TEST_F(LLVMCodeBuilderTest, GreaterAndLowerThanComparison)
         runOpTest(type, "1", 0);
         runOpTest(type, 0, "test");
         runOpTest(type, "test", 0);
+        runOpTest(type, 55, "abc");
+        runOpTest(type, "abc", 55);
 
         static const double inf = std::numeric_limits<double>::infinity();
         static const double nan = std::numeric_limits<double>::quiet_NaN();

From 953b0d9e2e50acd338d4688b359139a73a9d4528 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 01:00:46 +0100
Subject: [PATCH 226/259] LLVMCodeBuilder: Add list range checks

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  63 +++++++++-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 117 +++++++++++++++---
 2 files changed, 160 insertions(+), 20 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 0404f448..8a8f9fde 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -660,9 +660,25 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
+
+                // Range check
+                llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+                size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());
+                llvm::Value *index = castValue(arg.second, arg.first);
+                llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLT(index, size));
+                llvm::BasicBlock *removeBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                m_builder.CreateCondBr(inRange, removeBlock, nextBlock);
+
+                // Remove
+                m_builder.SetInsertPoint(removeBlock);
+                index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
                 m_builder.CreateCall(resolve_list_remove(), { listPtr.ptr, index });
                 // NOTE: Removing doesn't deallocate (see List::removeAt()), so there's no need to update the data pointer
+                m_builder.CreateBr(nextBlock);
+
+                m_builder.SetInsertPoint(nextBlock);
                 break;
             }
 
@@ -733,11 +749,23 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
                 m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpEQ(allocatedSize, size)), listPtr.dataPtrDirty);
 
+                // Range check
+                llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());
+                llvm::Value *index = castValue(indexArg.second, indexArg.first);
+                llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLE(index, size));
+                llvm::BasicBlock *insertBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                m_builder.CreateCondBr(inRange, insertBlock, nextBlock);
+
                 // Insert
-                llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
+                m_builder.SetInsertPoint(insertBlock);
+                index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty());
                 llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
                 createReusedValueStore(valueArg.second, itemPtr, type, listPtr.type);
+                m_builder.CreateBr(nextBlock);
 
+                m_builder.SetInsertPoint(nextBlock);
                 break;
             }
 
@@ -747,9 +775,23 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 const auto &valueArg = step.args[1];
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                llvm::Value *index = m_builder.CreateFPToUI(castValue(indexArg.second, indexArg.first), m_builder.getInt64Ty());
+
+                // Range check
+                llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+                size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());
+                llvm::Value *index = castValue(indexArg.second, indexArg.first);
+                llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLT(index, size));
+                llvm::BasicBlock *replaceBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function);
+                m_builder.CreateCondBr(inRange, replaceBlock, nextBlock);
+
+                // Replace
+                m_builder.SetInsertPoint(replaceBlock);
+                index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty());
                 llvm::Value *itemPtr = getListItem(listPtr, index);
                 createValueStore(valueArg.second, itemPtr, type, listPtr.type);
+                m_builder.CreateBr(nextBlock);
 
                 auto &typeMap = m_scopeLists.back();
 
@@ -761,6 +803,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     typeMap[&listPtr] = listPtr.type;
                 }
 
+                m_builder.SetInsertPoint(nextBlock);
                 break;
             }
 
@@ -777,8 +820,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
                 const LLVMListPtr &listPtr = m_listPtrs[step.workList];
-                llvm::Value *index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty());
-                step.functionReturnReg->value = getListItem(listPtr, index);
+
+                llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
+                size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());
+                llvm::Value *index = castValue(arg.second, arg.first);
+                llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLT(index, size));
+
+                LLVMConstantRegister nullReg(listPtr.type == Compiler::StaticType::Unknown ? Compiler::StaticType::Number : listPtr.type, Value());
+                llvm::Value *null = createValue(static_cast<LLVMRegister *>(static_cast<CompilerValue *>(&nullReg)));
+
+                index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty());
+                step.functionReturnReg->value = m_builder.CreateSelect(inRange, getListItem(listPtr, index), null);
                 step.functionReturnReg->setType(listPtr.type);
                 break;
             }
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index c8b9c916..4ff82788 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -1913,9 +1913,21 @@ TEST_F(LLVMCodeBuilderTest, RemoveFromList)
     CompilerValue *v = m_builder->addConstValue(1);
     m_builder->createListRemove(globalList.get(), v);
 
+    v = m_builder->addConstValue(-1);
+    m_builder->createListRemove(globalList.get(), v);
+
+    v = m_builder->addConstValue(3);
+    m_builder->createListRemove(globalList.get(), v);
+
     v = m_builder->addConstValue(3);
     m_builder->createListRemove(localList.get(), v);
 
+    v = m_builder->addConstValue(-1);
+    m_builder->createListRemove(localList.get(), v);
+
+    v = m_builder->addConstValue(4);
+    m_builder->createListRemove(localList.get(), v);
+
     auto code = m_builder->finalize();
     Script script(&sprite, nullptr, nullptr);
     script.setCode(code);
@@ -2040,6 +2052,18 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
     v2 = m_builder->addConstValue("hello world");
     m_builder->createListInsert(localList.get(), v1, v2);
 
+    v1 = m_builder->addConstValue(3);
+    v2 = m_builder->addConstValue("test");
+    m_builder->createListInsert(localList.get(), v1, v2);
+
+    v1 = m_builder->addConstValue(-1);
+    v2 = m_builder->addConstValue(123);
+    m_builder->createListInsert(localList.get(), v1, v2);
+
+    v1 = m_builder->addConstValue(6);
+    v2 = m_builder->addConstValue(123);
+    m_builder->createListInsert(localList.get(), v1, v2);
+
     auto code = m_builder->finalize();
     Script script(&sprite, nullptr, nullptr);
     script.setCode(code);
@@ -2049,7 +2073,7 @@ TEST_F(LLVMCodeBuilderTest, InsertToList)
     code->run(ctx.get());
 
     ASSERT_EQ(globalList->toString(), "1 2 1 test 3");
-    ASSERT_EQ(localList->toString(), "false hello world true");
+    ASSERT_EQ(localList->toString(), "false hello world true test");
 }
 
 TEST_F(LLVMCodeBuilderTest, ListReplace)
@@ -2099,6 +2123,14 @@ TEST_F(LLVMCodeBuilderTest, ListReplace)
     v2 = m_builder->addConstValue("hello world");
     m_builder->createListReplace(localList.get(), v1, v2);
 
+    v1 = m_builder->addConstValue(-1);
+    v2 = m_builder->addConstValue(123);
+    m_builder->createListReplace(localList.get(), v1, v2);
+
+    v1 = m_builder->addConstValue(5);
+    v2 = m_builder->addConstValue(123);
+    m_builder->createListReplace(localList.get(), v1, v2);
+
     auto code = m_builder->finalize();
     Script script(&sprite, nullptr, nullptr);
     script.setCode(code);
@@ -2169,26 +2201,36 @@ TEST_F(LLVMCodeBuilderTest, GetListItem)
     sprite.setEngine(&m_engine);
     EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
 
-    std::unordered_map<List *, std::string> strings;
-
     auto globalList = std::make_shared<List>("", "");
     stage.addList(globalList);
 
-    auto localList = std::make_shared<List>("", "");
-    sprite.addList(localList);
+    auto localList1 = std::make_shared<List>("", "");
+    sprite.addList(localList1);
+
+    auto localList2 = std::make_shared<List>("", "");
+    sprite.addList(localList2);
+
+    auto localList3 = std::make_shared<List>("", "");
+    sprite.addList(localList3);
 
     globalList->append(1);
     globalList->append(2);
     globalList->append(3);
 
-    localList->append("Lorem");
-    localList->append("ipsum");
-    localList->append("dolor");
-    localList->append("sit");
-    strings[localList.get()] = localList->toString();
+    localList1->append("Lorem");
+    localList1->append("ipsum");
+    localList1->append("dolor");
+    localList1->append("sit");
+
+    localList2->append(-564.121);
+    localList2->append(4257.4);
+
+    localList3->append(true);
+    localList3->append(false);
 
     createBuilder(&sprite, true);
 
+    // Global
     CompilerValue *v = m_builder->addConstValue(2);
     v = m_builder->addListItem(globalList.get(), v);
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
@@ -2201,24 +2243,67 @@ TEST_F(LLVMCodeBuilderTest, GetListItem)
     v = m_builder->addListItem(globalList.get(), v);
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
+    v = m_builder->addConstValue(-1);
+    v = m_builder->addListItem(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    v = m_builder->addConstValue(3);
+    v = m_builder->addListItem(globalList.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    // Local 1
     v = m_builder->addConstValue(0);
-    v = m_builder->addListItem(localList.get(), v);
+    v = m_builder->addListItem(localList1.get(), v);
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     v = m_builder->addConstValue(2);
-    v = m_builder->addListItem(localList.get(), v);
+    v = m_builder->addListItem(localList1.get(), v);
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
     v = m_builder->addConstValue(3);
-    v = m_builder->addListItem(localList.get(), v);
+    v = m_builder->addListItem(localList1.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    v = m_builder->addConstValue(-1);
+    v = m_builder->addListItem(localList1.get(), v);
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
+    v = m_builder->addConstValue(4);
+    v = m_builder->addListItem(localList1.get(), v);
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    // Local 2
+    v = m_builder->addConstValue(-1);
+    v = m_builder->addListItem(localList2.get(), v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+    v = m_builder->addConstValue(2);
+    v = m_builder->addListItem(localList2.get(), v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+    // Local 3
+    v = m_builder->addConstValue(-1);
+    v = m_builder->addListItem(localList3.get(), v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+    v = m_builder->addConstValue(2);
+    v = m_builder->addListItem(localList3.get(), v);
+    m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
     static const std::string expected =
         "3\n"
         "1\n"
+        "0\n"
+        "0\n"
         "Lorem\n"
         "dolor\n"
-        "sit\n";
+        "sit\n"
+        "0\n"
+        "0\n"
+        "0\n"
+        "0\n"
+        "0\n"
+        "0\n";
 
     auto code = m_builder->finalize();
     Script script(&sprite, nullptr, nullptr);
@@ -2231,7 +2316,9 @@ TEST_F(LLVMCodeBuilderTest, GetListItem)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 
     ASSERT_EQ(globalList->toString(), "1 test 3");
-    ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit");
+    ASSERT_EQ(localList1->toString(), "Lorem ipsum dolor sit");
+    ASSERT_EQ(localList2->toString(), "-564.121 4257.4");
+    ASSERT_EQ(localList3->toString(), "true false");
 }
 
 TEST_F(LLVMCodeBuilderTest, GetListSize)

From 44e5da74453a54832e57f38c08c705825d8e5a1e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 01:09:42 +0100
Subject: [PATCH 227/259] LLVMCodeBuilder: Optimize list data ptr dirty checks

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 8a8f9fde..73828008 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -645,7 +645,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
                 llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 m_builder.CreateCall(resolve_list_clear(), listPtr.ptr);
-                m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty);
 
                 // Clearing may deallocate, so check if the allocated size changed
                 llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
@@ -743,13 +742,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     typeMap[&listPtr] = listPtr.type;
                 }
 
-                // dataPtrDirty
-                llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
-                llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
-                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
-                m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpEQ(allocatedSize, size)), listPtr.dataPtrDirty);
+                llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
 
                 // Range check
+                llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
                 llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
                 size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy());
                 llvm::Value *index = castValue(indexArg.second, indexArg.first);
@@ -763,6 +759,12 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty());
                 llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index });
                 createReusedValueStore(valueArg.second, itemPtr, type, listPtr.type);
+
+                // Check if the allocated size changed
+                llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty);
+                llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
+                m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpNE(allocatedSize, oldAllocatedSize)), listPtr.dataPtrDirty);
+
                 m_builder.CreateBr(nextBlock);
 
                 m_builder.SetInsertPoint(nextBlock);

From bca2ab4efbfd4664b9a5225fba896e269d9a1e90 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 13:17:36 +0100
Subject: [PATCH 228/259] LLVMCodeBuilder: Add readonly attribute to some
 functions

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 63 +++++++++++++++----
 1 file changed, 51 insertions(+), 12 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 73828008..8a9f8374 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -2615,55 +2615,79 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_copy()
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toDouble()
 {
-    return resolveFunction("value_toDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), m_valueDataType->getPointerTo(), false));
+    llvm::FunctionCallee callee = resolveFunction("value_toDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), m_valueDataType->getPointerTo(), false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toBool()
 {
-    return resolveFunction("value_toBool", llvm::FunctionType::get(m_builder.getInt1Ty(), m_valueDataType->getPointerTo(), false));
+    llvm::FunctionCallee callee = resolveFunction("value_toBool", llvm::FunctionType::get(m_builder.getInt1Ty(), m_valueDataType->getPointerTo(), false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toCString()
 {
+    // NOTE: This function can't be marked read-only because it allocates on the heap
     return resolveFunction("value_toCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_valueDataType->getPointerTo(), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_doubleToCString()
 {
+    // NOTE: This function can't be marked read-only because it allocates on the heap
     return resolveFunction("value_doubleToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_builder.getDoubleTy(), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_boolToCString()
 {
+    // NOTE: This function can't be marked read-only because it allocates on the heap
     return resolveFunction("value_boolToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_builder.getInt1Ty(), false));
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToDouble()
 {
-    return resolveFunction("value_stringToDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false));
+    llvm::FunctionCallee callee = resolveFunction("value_stringToDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToBool()
 {
-    return resolveFunction("value_stringToBool", llvm::FunctionType::get(m_builder.getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false));
+    llvm::FunctionCallee callee = resolveFunction("value_stringToBool", llvm::FunctionType::get(m_builder.getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_equals()
 {
     llvm::Type *valuePtr = m_valueDataType->getPointerTo();
-    return resolveFunction("value_equals", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false));
+    llvm::FunctionCallee callee = resolveFunction("value_equals", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_greater()
 {
     llvm::Type *valuePtr = m_valueDataType->getPointerTo();
-    return resolveFunction("value_greater", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false));
+    llvm::FunctionCallee callee = resolveFunction("value_greater", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower()
 {
     llvm::Type *valuePtr = m_valueDataType->getPointerTo();
-    return resolveFunction("value_lower", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false));
+    llvm::FunctionCallee callee = resolveFunction("value_lower", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_clear()
@@ -2693,25 +2717,37 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_insert_empty()
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_data()
 {
     llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
-    return resolveFunction("list_data", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
+    llvm::FunctionCallee callee = resolveFunction("list_data", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size_ptr()
 {
     llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
-    return resolveFunction("list_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
+    llvm::FunctionCallee callee = resolveFunction("list_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_alloc_size_ptr()
 {
     llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
-    return resolveFunction("list_alloc_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
+    llvm::FunctionCallee callee = resolveFunction("list_alloc_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
-    return resolveFunction("list_to_string", llvm::FunctionType::get(pointerType, { pointerType }, false));
+    llvm::FunctionCallee callee = resolveFunction("list_to_string", llvm::FunctionType::get(pointerType, { pointerType }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }
 
 llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random()
@@ -2742,5 +2778,8 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool()
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
-    return resolveFunction("strcasecmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false));
+    llvm::FunctionCallee callee = resolveFunction("strcasecmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
 }

From b3740616aaf63baeac53dd052f94014299b7107d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 13:19:53 +0100
Subject: [PATCH 229/259] Use O3 for the entire LLVM module

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  6 ----
 .../internal/llvm/llvmcompilercontext.cpp     | 35 ++++---------------
 2 files changed, 7 insertions(+), 34 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 8a9f8374..05b95c74 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -80,12 +80,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
     if (m_procedurePrototype && m_warp)
         m_function->addFnAttr(llvm::Attribute::InlineHint);
-    else {
-        // NOTE: These attributes will be overriden by LLVMCompilerContext
-        // TODO: Optimize all functions, maybe it doesn't take so long
-        m_function->addFnAttr(llvm::Attribute::NoInline);
-        m_function->addFnAttr(llvm::Attribute::OptimizeNone);
-    }
 
     llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", m_function);
     llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", m_function);
diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
index 88e57d34..bda5bab9 100644
--- a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
+++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp
@@ -57,9 +57,6 @@ void LLVMCompilerContext::initJit()
 #endif
 
     // Optimize
-    const auto &functions = m_module->getFunctionList();
-    std::vector<std::string> lookupNames;
-
     llvm::PassBuilder passBuilder;
     llvm::LoopAnalysisManager loopAnalysisManager;
     llvm::FunctionAnalysisManager functionAnalysisManager;
@@ -72,33 +69,15 @@ void LLVMCompilerContext::initJit()
     passBuilder.registerLoopAnalyses(loopAnalysisManager);
     passBuilder.crossRegisterProxies(loopAnalysisManager, functionAnalysisManager, cGSCCAnalysisManager, moduleAnalysisManager);
 
-    // Use O0 for the entire module
-    llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O0);
-
-    // Create a FunctionPassManager for O3 optimizations
-    llvm::FunctionPassManager functionPassManager = passBuilder.buildFunctionSimplificationPipeline(llvm::OptimizationLevel::O3, llvm::ThinOrFullLTOPhase::None);
-
-    // Run the O0 pipeline for the module
+    llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3);
     modulePassManager.run(*m_module, moduleAnalysisManager);
 
-    // Run the O3 pipeline for specific functions
-    for (llvm::Function &func : m_module->functions()) {
-        if (!func.isDeclaration()) {
-            if (func.hasExternalLinkage()) {
-                lookupNames.push_back(func.getName().str());
-
-                if (func.hasFnAttribute(llvm::Attribute::OptimizeNone)) {
-                    // Remove this attribute to avoid JIT hangs
-                    // TODO: Optimize all functions, maybe it doesn't take so long
-                    func.removeFnAttr(llvm::Attribute::OptimizeNone);
-                } else {
-#ifndef NDEBUG
-                    std::cout << "debug: optimizing function: " << func.getName().str() << std::endl;
-#endif
-                    functionPassManager.run(func, functionAnalysisManager);
-                }
-            }
-        }
+    const auto &functions = m_module->getFunctionList();
+    std::vector<std::string> lookupNames;
+
+    for (const llvm::Function &func : functions) {
+        if (func.hasExternalLinkage() && !func.isDeclaration())
+            lookupNames.push_back(func.getName().str());
     }
 
     // Init JIT compiler

From 26a45402a2a8500e0a47861e1294acca3218f00a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 14:45:05 +0100
Subject: [PATCH 230/259] Remove range checks from list blocks

---
 src/dev/blocks/listblocks.cpp | 33 ++++++---------------------------
 1 file changed, 6 insertions(+), 27 deletions(-)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index 4da342d3..4529b015 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -81,16 +81,10 @@ CompilerValue *ListBlocks::compileDeleteOfList(Compiler *compiler)
         }
         compiler->beginElseBranch();
         {
-            CompilerValue *min = compiler->addConstValue(-1);
             CompilerValue *max = compiler->addListSize(list);
             index = getListIndex(compiler, index, list, max);
             index = compiler->createSub(index, compiler->addConstValue(1));
-            cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
-            compiler->beginIfStatement(cond);
-            {
-                compiler->createListRemove(list, index);
-            }
-            compiler->endIf();
+            compiler->createListRemove(list, index);
         }
         compiler->endIf();
     }
@@ -116,17 +110,11 @@ CompilerValue *ListBlocks::compileInsertAtList(Compiler *compiler)
 
     if (list) {
         CompilerValue *index = compiler->addInput("INDEX");
-        CompilerValue *min = compiler->addConstValue(-1);
         CompilerValue *max = compiler->createAdd(compiler->addListSize(list), compiler->addConstValue(1));
         index = getListIndex(compiler, index, list, max);
         index = compiler->createSub(index, compiler->addConstValue(1));
-        CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
-        compiler->beginIfStatement(cond);
-        {
-            CompilerValue *item = compiler->addInput("ITEM");
-            compiler->createListInsert(list, index, item);
-        }
-        compiler->endIf();
+        CompilerValue *item = compiler->addInput("ITEM");
+        compiler->createListInsert(list, index, item);
     }
 
     return nullptr;
@@ -139,17 +127,11 @@ CompilerValue *ListBlocks::compileReplaceItemOfList(Compiler *compiler)
 
     if (list) {
         CompilerValue *index = compiler->addInput("INDEX");
-        CompilerValue *min = compiler->addConstValue(-1);
         CompilerValue *max = compiler->addListSize(list);
         index = getListIndex(compiler, index, list, max);
         index = compiler->createSub(index, compiler->addConstValue(1));
-        CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
-        compiler->beginIfStatement(cond);
-        {
-            CompilerValue *item = compiler->addInput("ITEM");
-            compiler->createListReplace(list, index, item);
-        }
-        compiler->endIf();
+        CompilerValue *item = compiler->addInput("ITEM");
+        compiler->createListReplace(list, index, item);
     }
 
     return nullptr;
@@ -162,13 +144,10 @@ CompilerValue *ListBlocks::compileItemOfList(Compiler *compiler)
 
     if (list) {
         CompilerValue *index = compiler->addInput("INDEX");
-        CompilerValue *min = compiler->addConstValue(-1);
         CompilerValue *max = compiler->addListSize(list);
         index = getListIndex(compiler, index, list, max);
         index = compiler->createSub(index, compiler->addConstValue(1));
-        CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max));
-        CompilerValue *item = compiler->addListItem(list, index);
-        return compiler->createSelect(cond, item, compiler->addConstValue(Value()), Compiler::StaticType::Unknown);
+        return compiler->addListItem(list, index);
     }
 
     return nullptr;

From f2cdb0c587d0dc59f592255e7c04eba75ccde76c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 16:26:20 +0100
Subject: [PATCH 231/259] LLVMCodeBuilder: Add createStrCmpEQ() method

---
 src/dev/engine/internal/icodebuilder.h        |   2 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  69 +++++++-
 .../engine/internal/llvm/llvmcodebuilder.h    |   4 +
 .../engine/internal/llvm/llvminstruction.h    |   2 +
 test/dev/llvm/llvmcodebuilder_test.cpp        | 164 ++++++++++++++++++
 test/mocks/codebuildermock.h                  |   2 +
 6 files changed, 239 insertions(+), 4 deletions(-)

diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h
index 9f6cb567..fb41eb89 100644
--- a/src/dev/engine/internal/icodebuilder.h
+++ b/src/dev/engine/internal/icodebuilder.h
@@ -46,6 +46,8 @@ class ICodeBuilder
         virtual CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) = 0;
 
+        virtual CompilerValue *createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive = false) = 0;
+
         virtual CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2) = 0;
         virtual CompilerValue *createNot(CompilerValue *operand) = 0;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 05b95c74..e300f705 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -270,6 +270,22 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 break;
             }
 
+            case LLVMInstruction::Type::StrCmpEQCS: {
+                assert(step.args.size() == 2);
+                const auto &arg1 = step.args[0].second;
+                const auto &arg2 = step.args[1].second;
+                step.functionReturnReg->value = createStringComparison(arg1, arg2, true);
+                break;
+            }
+
+            case LLVMInstruction::Type::StrCmpEQCI: {
+                assert(step.args.size() == 2);
+                const auto &arg1 = step.args[0].second;
+                const auto &arg2 = step.args[1].second;
+                step.functionReturnReg->value = createStringComparison(arg1, arg2, false);
+                break;
+            }
+
             case LLVMInstruction::Type::And: {
                 assert(step.args.size() == 2);
                 const auto &arg1 = step.args[0];
@@ -1363,6 +1379,11 @@ CompilerValue *LLVMCodeBuilder::createCmpLT(CompilerValue *operand1, CompilerVal
     return createOp(LLVMInstruction::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 });
 }
 
+CompilerValue *LLVMCodeBuilder::createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive)
+{
+    return createOp(caseSensitive ? LLVMInstruction::Type::StrCmpEQCS : LLVMInstruction::Type::StrCmpEQCI, Compiler::StaticType::Bool, Compiler::StaticType::String, { string1, string2 });
+}
+
 CompilerValue *LLVMCodeBuilder::createAnd(CompilerValue *operand1, CompilerValue *operand2)
 {
     return createOp(LLVMInstruction::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, { operand1, operand2 });
@@ -2399,10 +2420,8 @@ llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegister *arg1, LLVMRegister
         // Optimize number and string constant comparison
         // TODO: GT and LT comparison can be optimized here (e. g. by checking the string constant characters and comparing with numbers and .+-e)
         if (type == Comparison::EQ) {
-            if (type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::String && arg2->isConst() && !arg2->constValue().isValidNumber())
-                return m_builder.getInt1(false);
-
-            if (type1 == Compiler::StaticType::String && type2 == Compiler::StaticType::Number && arg1->isConst() && !arg1->constValue().isValidNumber())
+            if ((type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::String && arg2->isConst() && !arg2->constValue().isValidNumber()) ||
+                (type1 == Compiler::StaticType::String && type2 == Compiler::StaticType::Number && arg1->isConst() && !arg1->constValue().isValidNumber()))
                 return m_builder.getInt1(false);
         }
 
@@ -2535,6 +2554,39 @@ llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegister *arg1, LLVMRegister
     }
 }
 
+llvm::Value *LLVMCodeBuilder::createStringComparison(LLVMRegister *arg1, LLVMRegister *arg2, bool caseSensitive)
+{
+    auto type1 = arg1->type();
+    auto type2 = arg2->type();
+
+    if (arg1->isConst() && arg2->isConst()) {
+        // If both operands are constant, perform the comparison at compile time
+        bool result;
+
+        if (caseSensitive)
+            result = arg1->constValue().toString() == arg2->constValue().toString();
+        else {
+            std::string str1 = arg1->constValue().toString();
+            std::string str2 = arg2->constValue().toString();
+            result = strcasecmp(str1.c_str(), str2.c_str()) == 0;
+        }
+
+        return m_builder.getInt1(result);
+    } else {
+        // Optimize number and string constant comparison
+        // TODO: Optimize bool and string constant comparison (in compare() as well)
+        if ((type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::String && arg2->isConst() && !arg2->constValue().isValidNumber()) ||
+            (type1 == Compiler::StaticType::String && type2 == Compiler::StaticType::Number && arg1->isConst() && !arg1->constValue().isValidNumber()))
+            return m_builder.getInt1(false);
+
+        // Explicitly cast to string
+        llvm::Value *string1 = castValue(arg1, Compiler::StaticType::String);
+        llvm::Value *string2 = castValue(arg2, Compiler::StaticType::String);
+        llvm::Value *cmp = m_builder.CreateCall(caseSensitive ? resolve_strcmp() : resolve_strcasecmp(), { string1, string2 });
+        return m_builder.CreateICmpEQ(cmp, m_builder.getInt32(0));
+    }
+}
+
 void LLVMCodeBuilder::createSuspend(LLVMCoroutine *coro, llvm::Value *warpArg, llvm::Value *targetVariables)
 {
     if (!m_warp) {
@@ -2769,6 +2821,15 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool()
     return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false));
 }
 
+llvm::FunctionCallee LLVMCodeBuilder::resolve_strcmp()
+{
+    llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
+    llvm::FunctionCallee callee = resolveFunction("strcmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false));
+    llvm::Function *func = llvm::cast<llvm::Function>(callee.getCallee());
+    func->addFnAttr(llvm::Attribute::ReadOnly);
+    return callee;
+}
+
 llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp()
 {
     llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 9728b6cb..f6a6142e 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -54,6 +54,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) override;
 
+        CompilerValue *createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive = false) override;
+
         CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2) override;
         CompilerValue *createNot(CompilerValue *operand) override;
@@ -156,6 +158,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item);
         llvm::Value *createValue(LLVMRegister *reg);
         llvm::Value *createComparison(LLVMRegister *arg1, LLVMRegister *arg2, Comparison type);
+        llvm::Value *createStringComparison(LLVMRegister *arg1, LLVMRegister *arg2, bool caseSensitive);
 
         void createSuspend(LLVMCoroutine *coro, llvm::Value *warpArg, llvm::Value *targetVariables);
 
@@ -190,6 +193,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::FunctionCallee resolve_llvm_random_double();
         llvm::FunctionCallee resolve_llvm_random_long();
         llvm::FunctionCallee resolve_llvm_random_bool();
+        llvm::FunctionCallee resolve_strcmp();
         llvm::FunctionCallee resolve_strcasecmp();
 
         Target *m_target = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index cb8b7ce5..ddb39677 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -25,6 +25,8 @@ struct LLVMInstruction
             CmpEQ,
             CmpGT,
             CmpLT,
+            StrCmpEQCS, // case sensitive
+            StrCmpEQCI, // case insensitive
             And,
             Or,
             Not,
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 4ff82788..723c8101 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -36,6 +36,8 @@ class LLVMCodeBuilderTest : public testing::Test
             CmpEQ,
             CmpGT,
             CmpLT,
+            StrCmpEQCS,
+            StrCmpEQCI,
             And,
             Or,
             Not,
@@ -127,6 +129,12 @@ class LLVMCodeBuilderTest : public testing::Test
                 case OpType::CmpLT:
                     return m_builder->createCmpLT(arg1, arg2);
 
+                case OpType::StrCmpEQCS:
+                    return m_builder->createStrCmpEQ(arg1, arg2, true);
+
+                case OpType::StrCmpEQCI:
+                    return m_builder->createStrCmpEQ(arg1, arg2, false);
+
                 case OpType::And:
                     return m_builder->createAnd(arg1, arg2);
 
@@ -235,6 +243,16 @@ class LLVMCodeBuilderTest : public testing::Test
                 case OpType::CmpLT:
                     return v1 < v2;
 
+                case OpType::StrCmpEQCS:
+                    return v1.toString() == v2.toString();
+
+                case OpType::StrCmpEQCI: {
+                    // TODO: Use a custom function for string comparison
+                    std::string str1 = v1.toString();
+                    std::string str2 = v2.toString();
+                    return strcasecmp(str1.c_str(), str2.c_str()) == 0;
+                }
+
                 case OpType::And:
                     return v1.toBool() && v2.toBool();
 
@@ -1089,6 +1107,152 @@ TEST_F(LLVMCodeBuilderTest, GreaterAndLowerThanComparison)
     }
 }
 
+TEST_F(LLVMCodeBuilderTest, StringEqualComparison)
+{
+    std::vector<OpType> types = { OpType::StrCmpEQCS, OpType::StrCmpEQCI };
+
+    for (OpType type : types) {
+        runOpTest(type, 10, 10);
+        runOpTest(type, 10, 8);
+        runOpTest(type, 8, 10);
+
+        runOpTest(type, -4.25, -4.25);
+        runOpTest(type, -4.25, 5.312);
+        runOpTest(type, 5.312, -4.25);
+
+        runOpTest(type, true, true);
+        runOpTest(type, true, false);
+        runOpTest(type, false, true);
+
+        runOpTest(type, 1, true);
+        runOpTest(type, 1, false);
+
+        runOpTest(type, "abC def", "abC def");
+        runOpTest(type, "abC def", "abc dEf");
+        runOpTest(type, "abC def", "ghi Jkl");
+        runOpTest(type, "abC def", "hello world");
+
+        runOpTest(type, " ", "");
+        runOpTest(type, " ", "0");
+        runOpTest(type, " ", 0);
+        runOpTest(type, 0, " ");
+        runOpTest(type, "", "0");
+        runOpTest(type, "", 0);
+        runOpTest(type, 0, "");
+        runOpTest(type, "0", 0);
+        runOpTest(type, 0, "0");
+
+        runOpTest(type, 5.25, "5.25");
+        runOpTest(type, "5.25", 5.25);
+        runOpTest(type, 5.25, " 5.25");
+        runOpTest(type, " 5.25", 5.25);
+        runOpTest(type, 5.25, "5.25 ");
+        runOpTest(type, "5.25 ", 5.25);
+        runOpTest(type, 5.25, " 5.25 ");
+        runOpTest(type, " 5.25 ", 5.25);
+        runOpTest(type, 5.25, "5.26");
+        runOpTest(type, "5.26", 5.25);
+        runOpTest(type, "5.25", "5.26");
+        runOpTest(type, 5, "5  ");
+        runOpTest(type, "5  ", 5);
+        runOpTest(type, 0, "1");
+        runOpTest(type, "1", 0);
+        runOpTest(type, 0, "test");
+        runOpTest(type, "test", 0);
+
+        static const double inf = std::numeric_limits<double>::infinity();
+        static const double nan = std::numeric_limits<double>::quiet_NaN();
+
+        runOpTest(type, inf, inf);
+        runOpTest(type, -inf, -inf);
+        runOpTest(type, nan, nan);
+        runOpTest(type, inf, -inf);
+        runOpTest(type, -inf, inf);
+        runOpTest(type, inf, nan);
+        runOpTest(type, nan, inf);
+        runOpTest(type, -inf, nan);
+        runOpTest(type, nan, -inf);
+
+        runOpTest(type, 5, inf);
+        runOpTest(type, 5, -inf);
+        runOpTest(type, 5, nan);
+        runOpTest(type, 0, nan);
+
+        runOpTest(type, true, "true");
+        runOpTest(type, "true", true);
+        runOpTest(type, false, "false");
+        runOpTest(type, "false", false);
+        runOpTest(type, false, "true");
+        runOpTest(type, "true", false);
+        runOpTest(type, true, "false");
+        runOpTest(type, "false", true);
+        runOpTest(type, true, "TRUE");
+        runOpTest(type, "TRUE", true);
+        runOpTest(type, false, "FALSE");
+        runOpTest(type, "FALSE", false);
+
+        runOpTest(type, true, "00001");
+        runOpTest(type, "00001", true);
+        runOpTest(type, true, "00000");
+        runOpTest(type, "00000", true);
+        runOpTest(type, false, "00000");
+        runOpTest(type, "00000", false);
+
+        runOpTest(type, "true", 1);
+        runOpTest(type, 1, "true");
+        runOpTest(type, "true", 0);
+        runOpTest(type, 0, "true");
+        runOpTest(type, "false", 0);
+        runOpTest(type, 0, "false");
+        runOpTest(type, "false", 1);
+        runOpTest(type, 1, "false");
+
+        runOpTest(type, "true", "TRUE");
+        runOpTest(type, "true", "FALSE");
+        runOpTest(type, "false", "FALSE");
+        runOpTest(type, "false", "TRUE");
+
+        runOpTest(type, true, inf);
+        runOpTest(type, true, -inf);
+        runOpTest(type, true, nan);
+        runOpTest(type, false, inf);
+        runOpTest(type, false, -inf);
+        runOpTest(type, false, nan);
+
+        runOpTest(type, "Infinity", inf);
+        runOpTest(type, "Infinity", -inf);
+        runOpTest(type, "Infinity", nan);
+        runOpTest(type, "infinity", inf);
+        runOpTest(type, "infinity", -inf);
+        runOpTest(type, "infinity", nan);
+        runOpTest(type, "-Infinity", inf);
+        runOpTest(type, "-Infinity", -inf);
+        runOpTest(type, "-Infinity", nan);
+        runOpTest(type, "-infinity", inf);
+        runOpTest(type, "-infinity", -inf);
+        runOpTest(type, "-infinity", nan);
+        runOpTest(type, "NaN", inf);
+        runOpTest(type, "NaN", -inf);
+        runOpTest(type, "NaN", nan);
+        runOpTest(type, "nan", inf);
+        runOpTest(type, "nan", -inf);
+        runOpTest(type, "nan", nan);
+
+        runOpTest(type, inf, "abc");
+        runOpTest(type, inf, " ");
+        runOpTest(type, inf, "");
+        runOpTest(type, inf, "0");
+        runOpTest(type, -inf, "abc");
+        runOpTest(type, -inf, " ");
+        runOpTest(type, -inf, "");
+        runOpTest(type, -inf, "0");
+        runOpTest(type, nan, "abc");
+        runOpTest(type, nan, " ");
+        runOpTest(type, nan, "");
+        runOpTest(type, nan, "0");
+    }
+}
+
 TEST_F(LLVMCodeBuilderTest, AndOr)
 {
     std::vector<OpType> opTypes = { OpType::And, OpType::Or };
diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h
index 63455dda..6ce454bf 100644
--- a/test/mocks/codebuildermock.h
+++ b/test/mocks/codebuildermock.h
@@ -35,6 +35,8 @@ class CodeBuilderMock : public ICodeBuilder
         MOCK_METHOD(CompilerValue *, createCmpGT, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createCmpLT, (CompilerValue *, CompilerValue *), (override));
 
+        MOCK_METHOD(CompilerValue *, createStrCmpEQ, (CompilerValue *, CompilerValue *, bool), (override));
+
         MOCK_METHOD(CompilerValue *, createAnd, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createOr, (CompilerValue *, CompilerValue *), (override));
         MOCK_METHOD(CompilerValue *, createNot, (CompilerValue *), (override));

From 61c44968d3124f649fdaa7de762bc1d430f811bb Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 16:29:54 +0100
Subject: [PATCH 232/259] Compiler: Add createStrCmpEQ() method

---
 include/scratchcpp/dev/compiler.h   |  2 ++
 src/dev/engine/compiler.cpp         |  6 ++++++
 test/dev/compiler/compiler_test.cpp | 21 +++++++++++++++++++++
 3 files changed, 29 insertions(+)

diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h
index 203b4f0f..db7ada83 100644
--- a/include/scratchcpp/dev/compiler.h
+++ b/include/scratchcpp/dev/compiler.h
@@ -81,6 +81,8 @@ class LIBSCRATCHCPP_EXPORT Compiler
         CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2);
 
+        CompilerValue *createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive = false);
+
         CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2);
         CompilerValue *createNot(CompilerValue *operand);
diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp
index 730679e9..23fc0045 100644
--- a/src/dev/engine/compiler.cpp
+++ b/src/dev/engine/compiler.cpp
@@ -324,6 +324,12 @@ CompilerValue *Compiler::createCmpLT(CompilerValue *operand1, CompilerValue *ope
     return impl->builder->createCmpLT(operand1, operand2);
 }
 
+/*! Creates a string equality comparison (explicitly casts operands to string). */
+CompilerValue *Compiler::createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive)
+{
+    return impl->builder->createStrCmpEQ(string1, string2, caseSensitive);
+}
+
 /*! Creates an AND operation. */
 CompilerValue *Compiler::createAnd(CompilerValue *operand1, CompilerValue *operand2)
 {
diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp
index e3c017f6..219a608b 100644
--- a/test/dev/compiler/compiler_test.cpp
+++ b/test/dev/compiler/compiler_test.cpp
@@ -610,6 +610,27 @@ TEST_F(CompilerTest, CreateCmpLT)
     compile(m_compiler.get(), block);
 }
 
+TEST_F(CompilerTest, CreateStrCmpEQ)
+{
+
+    auto block = std::make_shared<Block>("", "");
+
+    block->setCompileFunction([](Compiler *compiler) -> CompilerValue * {
+        CompilerValue arg1(Compiler::StaticType::Unknown);
+        CompilerValue arg2(Compiler::StaticType::Unknown);
+        CompilerValue ret(Compiler::StaticType::Unknown);
+        EXPECT_CALL(*m_builder, createStrCmpEQ(&arg1, &arg2, true)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createStrCmpEQ(&arg1, &arg2, true), &ret);
+
+        EXPECT_CALL(*m_builder, createStrCmpEQ(&arg1, &arg2, false)).WillOnce(Return(&ret));
+        EXPECT_EQ(compiler->createStrCmpEQ(&arg1, &arg2, false), &ret);
+
+        return nullptr;
+    });
+
+    compile(m_compiler.get(), block);
+}
+
 TEST_F(CompilerTest, CreateAnd)
 {
 

From be9846a6bb70b149f4f8f6e329b1ba0992f7bf27 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 16:30:25 +0100
Subject: [PATCH 233/259] Use case sensitive string comparison in list blocks

---
 src/dev/blocks/listblocks.cpp        |  8 ++++----
 test/dev/blocks/list_blocks_test.cpp | 23 ++++++++++++++++++++++-
 2 files changed, 26 insertions(+), 5 deletions(-)

diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index 4529b015..653875e4 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -48,8 +48,8 @@ CompilerValue *ListBlocks::getListIndex(Compiler *compiler, CompilerValue *input
 {
     CompilerLocalVariable *ret = compiler->createLocalVariable(Compiler::StaticType::Number);
 
-    CompilerValue *isRandom1 = compiler->createCmpEQ(input, compiler->addConstValue("random"));
-    CompilerValue *isRandom2 = compiler->createCmpEQ(input, compiler->addConstValue("any"));
+    CompilerValue *isRandom1 = compiler->createStrCmpEQ(input, compiler->addConstValue("random"), true);
+    CompilerValue *isRandom2 = compiler->createStrCmpEQ(input, compiler->addConstValue("any"), true);
     CompilerValue *isRandom = compiler->createOr(isRandom1, isRandom2);
 
     compiler->beginIfStatement(isRandom);
@@ -59,7 +59,7 @@ CompilerValue *ListBlocks::getListIndex(Compiler *compiler, CompilerValue *input
     }
     compiler->beginElseBranch();
     {
-        CompilerValue *isLast = compiler->createCmpEQ(input, compiler->addConstValue("last"));
+        CompilerValue *isLast = compiler->createStrCmpEQ(input, compiler->addConstValue("last"), true);
         compiler->createLocalVariableWrite(ret, compiler->createSelect(isLast, listSize, input, Compiler::StaticType::Number));
     }
     compiler->endIf();
@@ -74,7 +74,7 @@ CompilerValue *ListBlocks::compileDeleteOfList(Compiler *compiler)
 
     if (list) {
         CompilerValue *index = compiler->addInput("INDEX");
-        CompilerValue *cond = compiler->createCmpEQ(index, compiler->addConstValue("all"));
+        CompilerValue *cond = compiler->createStrCmpEQ(index, compiler->addConstValue("all"), true);
         compiler->beginIfStatement(cond);
         {
             compiler->createListClear(list);
diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp
index bccc5bb1..583f2a1f 100644
--- a/test/dev/blocks/list_blocks_test.cpp
+++ b/test/dev/blocks/list_blocks_test.cpp
@@ -121,6 +121,12 @@ TEST_F(ListBlocksTest, DeleteOfList)
 
     addTest("all", list3);
 
+    addTest("Last", list2);
+    addTest("raNdom", list2);
+    addTest("aNY", list2);
+
+    addTest("aLl", list3);
+
     builder.build();
 
     Compiler compiler(&m_engineMock, target.get());
@@ -200,6 +206,10 @@ TEST_F(ListBlocksTest, InsertAtList)
     addTest("random", "ipsum", list2);
     addTest("any", "dolor", list2);
 
+    addTest("lAsT", "lorem", list2);
+    addTest("raNDom", "ipsum", list2);
+    addTest("Any", "dolor", list2);
+
     builder.build();
 
     Compiler compiler(&m_engineMock, target.get());
@@ -257,6 +267,10 @@ TEST_F(ListBlocksTest, ReplaceItemOfList)
     addTest("random", "ipsum", list2);
     addTest("any", "dolor", list2);
 
+    addTest("LasT", "lorem", list2);
+    addTest("rAndOm", "ipsum", list2);
+    addTest("AnY", "dolor", list2);
+
     builder.build();
 
     Compiler compiler(&m_engineMock, target.get());
@@ -311,6 +325,10 @@ TEST_F(ListBlocksTest, ItemOfList)
     addTest("random", list);
     addTest("any", list);
 
+    addTest("laSt", list);
+    addTest("RAndom", list);
+    addTest("aNy", list);
+
     builder.build();
 
     Compiler compiler(&m_engineMock, target.get());
@@ -328,7 +346,10 @@ TEST_F(ListBlocksTest, ItemOfList)
         "0\n"
         "true\n"
         "123\n"
-        "Lorem\n";
+        "Lorem\n"
+        "0\n"
+        "0\n"
+        "0\n";
 
     EXPECT_CALL(m_rng, randint(1, 5)).WillOnce(Return(4)).WillOnce(Return(1));
     testing::internal::CaptureStdout();

From 8d2458af16518e3d3e9951841d9f1f3fa6844fb6 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 22:38:24 +0100
Subject: [PATCH 234/259] ProjectDownloader: Fix asset downloading getting
 stuck when cancelled

---
 src/internal/projectdownloader.cpp | 21 +++------------------
 src/internal/projectdownloader.h   |  3 +--
 2 files changed, 4 insertions(+), 20 deletions(-)

diff --git a/src/internal/projectdownloader.cpp b/src/internal/projectdownloader.cpp
index c334da55..28a618d5 100644
--- a/src/internal/projectdownloader.cpp
+++ b/src/internal/projectdownloader.cpp
@@ -17,14 +17,11 @@ static const std::string ASSET_PREFIX = "https://assets.scratch.mit.edu/internal
 static const std::string ASSET_SUFFIX = "/get";
 
 #define CHECK_CANCEL()                                                                                                                                                                                 \
-    m_cancelMutex.lock();                                                                                                                                                                              \
     if (m_cancel) {                                                                                                                                                                                    \
         m_downloadedAssetCount = 0;                                                                                                                                                                    \
-        m_cancelMutex.unlock();                                                                                                                                                                        \
         std::cout << "Download aborted!" << std::endl;                                                                                                                                                 \
         return false;                                                                                                                                                                                  \
-    }                                                                                                                                                                                                  \
-    m_cancelMutex.unlock()
+    }
 
 ProjectDownloader::ProjectDownloader(IDownloaderFactory *downloaderFactory) :
     m_downloaderFactory(downloaderFactory)
@@ -38,9 +35,7 @@ ProjectDownloader::ProjectDownloader(IDownloaderFactory *downloaderFactory) :
 
 bool ProjectDownloader::downloadJson(const std::string &projectId)
 {
-    m_cancelMutex.lock();
     m_cancel = false;
-    m_cancelMutex.unlock();
 
     // Get project token
     std::cout << "Fetching project info of " << projectId << std::endl;
@@ -89,9 +84,7 @@ bool ProjectDownloader::downloadJson(const std::string &projectId)
 
 bool ProjectDownloader::downloadAssets(const std::vector<std::string> &assetIds)
 {
-    m_cancelMutex.lock();
     m_cancel = false;
-    m_cancelMutex.unlock();
 
     auto count = assetIds.size();
     // unsigned int threadCount = std::thread::hardware_concurrency();
@@ -119,20 +112,14 @@ bool ProjectDownloader::downloadAssets(const std::vector<std::string> &assetIds)
 
     // Download assets
     auto f = [this, count](std::shared_ptr<IDownloader> downloader, int index, const std::string &id) {
-        m_cancelMutex.lock();
-
         if (m_cancel)
             return;
 
-        m_cancelMutex.unlock();
-
         bool ret = downloader->download(ASSET_PREFIX + id + ASSET_SUFFIX);
 
         if (!ret) {
             std::cerr << "Failed to download asset: " << id << std::endl;
-            m_cancelMutex.lock();
             m_cancel = true;
-            m_cancelMutex.unlock();
             return;
         }
 
@@ -178,7 +165,7 @@ bool ProjectDownloader::downloadAssets(const std::vector<std::string> &assetIds)
 
         m_assetsMutex.lock();
 
-        if (m_downloadedAssetCount != lastCount) {
+        if (m_downloadedAssetCount != lastCount && !m_cancel) {
             std::cout << "Downloaded assets: " << m_downloadedAssetCount << " of " << count << std::endl;
             lastCount = m_downloadedAssetCount;
         }
@@ -197,7 +184,7 @@ bool ProjectDownloader::downloadAssets(const std::vector<std::string> &assetIds)
             threads.erase(index);
         }
 
-        if (done) {
+        if (done || m_cancel) {
             for (auto &[index, info] : threads)
                 info.first.join();
 
@@ -212,9 +199,7 @@ bool ProjectDownloader::downloadAssets(const std::vector<std::string> &assetIds)
 
 void ProjectDownloader::cancel()
 {
-    m_cancelMutex.lock();
     m_cancel = true;
-    m_cancelMutex.unlock();
     m_downloadedAssetCount = 0;
 }
 
diff --git a/src/internal/projectdownloader.h b/src/internal/projectdownloader.h
index 15b47472..07893458 100644
--- a/src/internal/projectdownloader.h
+++ b/src/internal/projectdownloader.h
@@ -36,8 +36,7 @@ class ProjectDownloader : public IProjectDownloader
         std::vector<std::string> m_assets;
         std::mutex m_assetsMutex;
         std::atomic<unsigned int> m_downloadedAssetCount = 0;
-        bool m_cancel = false;
-        std::mutex m_cancelMutex;
+        std::atomic<bool> m_cancel = false;
         sigslot::signal<unsigned int, unsigned int> m_downloadProgressChanged;
 };
 

From a7fb0cee51d8696467f3158af74c830af1fdb6b8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 12 Jan 2025 22:42:39 +0100
Subject: [PATCH 235/259] Project: Add stopLoading() method

---
 include/scratchcpp/project.h  |  1 +
 src/project.cpp               | 10 ++++++++
 src/project_p.cpp             | 43 ++++++++++++++++++++++++++++++++++-
 src/project_p.h               |  2 ++
 test/project/project_test.cpp | 14 ++++++++++++
 5 files changed, 69 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/project.h b/include/scratchcpp/project.h
index 6f1c455c..10a3e365 100644
--- a/include/scratchcpp/project.h
+++ b/include/scratchcpp/project.h
@@ -29,6 +29,7 @@ class LIBSCRATCHCPP_EXPORT Project
         Project(const Project &) = delete;
 
         bool load();
+        void stopLoading();
 
         void start();
         void run();
diff --git a/src/project.cpp b/src/project.cpp
index 221d8f1a..80bac15b 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -1,8 +1,10 @@
 // SPDX-License-Identifier: Apache-2.0
 
 #include <scratchcpp/project.h>
+#include <iostream>
 
 #include "project_p.h"
+#include "internal/projectdownloader.h"
 
 using namespace libscratchcpp;
 
@@ -27,6 +29,14 @@ bool Project::load()
     return impl->load();
 }
 
+/*! Cancels project loading if loading in another thread. */
+void Project::stopLoading()
+{
+    std::cout << "Aborting project loading..." << std::endl;
+    impl->stopLoading = true;
+    impl->downloader->cancel();
+}
+
 /*!
  * Calls all "when green flag clicked" blocks.
  * \note Nothing will happen until run() or frame() is called.
diff --git a/src/project_p.cpp b/src/project_p.cpp
index e3450fb4..c8c40e20 100644
--- a/src/project_p.cpp
+++ b/src/project_p.cpp
@@ -47,6 +47,8 @@ bool ProjectPrivate::load()
 
 bool ProjectPrivate::tryLoad(IProjectReader *reader)
 {
+    stopLoading = false;
+
     // Load from URL
     ProjectUrl url(fileName);
 
@@ -57,8 +59,18 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader)
             return false;
         }
 
+        if (stopLoading) {
+            loadingAborted();
+            return false;
+        }
+
         bool ret = reader->loadData(downloader->json());
 
+        if (stopLoading) {
+            loadingAborted();
+            return false;
+        }
+
         if (!ret)
             return false;
 
@@ -91,7 +103,14 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader)
         }
 
         // Download assets
-        if (!downloader->downloadAssets(assetNames)) {
+        ret = downloader->downloadAssets(assetNames);
+
+        if (stopLoading) {
+            loadingAborted();
+            return false;
+        }
+
+        if (!ret) {
             std::cerr << "Failed to download the project assets." << std::endl;
             return false;
         }
@@ -113,7 +132,18 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader)
             return false;
         }
 
+        if (stopLoading) {
+            loadingAborted();
+            return false;
+        }
+
         bool ret = reader->load();
+
+        if (stopLoading) {
+            loadingAborted();
+            return false;
+        }
+
         if (!ret)
             return false;
     }
@@ -125,9 +155,20 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader)
     engine->setExtensions(reader->extensions());
     engine->setUserAgent(reader->userAgent());
     engine->compile();
+
+    if (stopLoading) {
+        loadingAborted();
+        return false;
+    }
+
     return true;
 }
 
+void ProjectPrivate::loadingAborted()
+{
+    std::cout << "Loading aborted." << std::endl;
+}
+
 void ProjectPrivate::start()
 {
     engine->start();
diff --git a/src/project_p.h b/src/project_p.h
index d935835c..cb937f3a 100644
--- a/src/project_p.h
+++ b/src/project_p.h
@@ -21,6 +21,7 @@ struct ProjectPrivate
 
         bool load();
         bool tryLoad(IProjectReader *reader);
+        void loadingAborted();
 
         void start();
         void run();
@@ -29,6 +30,7 @@ struct ProjectPrivate
         sigslot::signal<unsigned int, unsigned int> &downloadProgressChanged();
 
         std::string fileName;
+        std::atomic<bool> stopLoading = false;
         std::shared_ptr<IEngine> engine = nullptr;
 
         static IProjectDownloaderFactory *downloaderFactory;
diff --git a/test/project/project_test.cpp b/test/project/project_test.cpp
index 93f350f2..e1129238 100644
--- a/test/project/project_test.cpp
+++ b/test/project/project_test.cpp
@@ -115,3 +115,17 @@ TEST(LoadProjectTest, DownloadProgressChanged)
     EXPECT_CALL(*downloader, downloadProgressChanged).WillOnce(ReturnRef(signal));
     ASSERT_EQ(&p.downloadProgressChanged(), &signal);
 }
+
+TEST(LoadProjectTest, AbortDownload)
+{
+    ProjectDownloaderFactoryMock factory;
+    auto downloader = std::make_shared<ProjectDownloaderMock>();
+    ProjectPrivate::downloaderFactory = &factory;
+
+    EXPECT_CALL(factory, create()).WillOnce(Return(downloader));
+    Project p;
+    ProjectPrivate::downloaderFactory = nullptr;
+
+    EXPECT_CALL(*downloader, cancel());
+    p.stopLoading();
+}

From acff91c6643498a3d90365e157ec38f284d8f103 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 13 Jan 2025 10:24:53 +0100
Subject: [PATCH 236/259] Move target property from Sound to Asset

Resolves: #540
---
 include/scratchcpp/asset.h |  4 ++++
 include/scratchcpp/sound.h |  4 ----
 src/scratch/asset.cpp      | 12 ++++++++++++
 src/scratch/asset_p.h      |  3 +++
 src/scratch/sound.cpp      | 18 ++++--------------
 src/scratch/sound_p.h      |  2 --
 test/assets/asset_test.cpp | 11 +++++++++++
 test/assets/sound_test.cpp | 10 ----------
 8 files changed, 34 insertions(+), 30 deletions(-)

diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h
index 975c3e16..c5c10d88 100644
--- a/include/scratchcpp/asset.h
+++ b/include/scratchcpp/asset.h
@@ -9,6 +9,7 @@
 namespace libscratchcpp
 {
 
+class Target;
 class AssetPrivate;
 
 /*! \brief The Asset class represents a Scratch asset, for example a Costume or a Sound. */
@@ -32,6 +33,9 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity
         unsigned int dataSize() const;
         void setData(unsigned int size, void *data);
 
+        Target *target() const;
+        void setTarget(Target *target);
+
     protected:
         virtual void processData(unsigned int size, void *data) { }
 
diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h
index 90f5abc7..b4906b1e 100644
--- a/include/scratchcpp/sound.h
+++ b/include/scratchcpp/sound.h
@@ -9,7 +9,6 @@
 namespace libscratchcpp
 {
 
-class Target;
 class SoundPrivate;
 
 /*! \brief The Sound class represents a Scratch sound. */
@@ -40,9 +39,6 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset
 
         virtual bool isPlaying() const;
 
-        Target *target() const;
-        void setTarget(Target *target);
-
         std::shared_ptr<Sound> clone() const;
 
     protected:
diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp
index bad195a4..79ffd4b1 100644
--- a/src/scratch/asset.cpp
+++ b/src/scratch/asset.cpp
@@ -58,3 +58,15 @@ void Asset::setData(unsigned int size, void *data)
     impl->data = data;
     processData(size, data);
 }
+
+/*! Returns the sprite or stage this asset belongs to. */
+Target *Asset::target() const
+{
+    return impl->target;
+}
+
+/*! Sets the sprite or stage this asset belongs to. */
+void Asset::setTarget(Target *target)
+{
+    impl->target = target;
+}
diff --git a/src/scratch/asset_p.h b/src/scratch/asset_p.h
index 81cd3219..d004fb2f 100644
--- a/src/scratch/asset_p.h
+++ b/src/scratch/asset_p.h
@@ -7,6 +7,8 @@
 namespace libscratchcpp
 {
 
+class Target;
+
 struct AssetPrivate
 {
         AssetPrivate(const std::string &name, const std::string &format);
@@ -19,6 +21,7 @@ struct AssetPrivate
         std::string fileName;
         const void *data = nullptr;
         unsigned int dataSize = 0;
+        Target *target = nullptr;
 };
 
 } // namespace libscratchcpp
diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp
index eca5e8b9..64fcf35a 100644
--- a/src/scratch/sound.cpp
+++ b/src/scratch/sound.cpp
@@ -87,18 +87,6 @@ bool Sound::isPlaying() const
     return impl->player->isPlaying();
 }
 
-/*! Returns the sprite or stage this variable belongs to. */
-Target *Sound::target() const
-{
-    return impl->target;
-}
-
-/*! Sets the sprite or stage this variable belongs to. */
-void Sound::setTarget(Target *target)
-{
-    impl->target = target;
-}
-
 /*! Returns an independent copy of the sound which is valid for as long as the original sound exists. */
 std::shared_ptr<Sound> Sound::clone() const
 {
@@ -130,8 +118,10 @@ void Sound::processData(unsigned int size, void *data)
 
 void Sound::stopCloneSounds()
 {
-    if (impl->target && !impl->target->isStage()) {
-        Sprite *sprite = static_cast<Sprite *>(impl->target);
+    Target *target = this->target();
+
+    if (target && !target->isStage()) {
+        Sprite *sprite = static_cast<Sprite *>(target);
 
         if (sprite->isClone())
             sprite = sprite->cloneSprite();
diff --git a/src/scratch/sound_p.h b/src/scratch/sound_p.h
index 3df2ca3f..9f2b460c 100644
--- a/src/scratch/sound_p.h
+++ b/src/scratch/sound_p.h
@@ -10,7 +10,6 @@
 namespace libscratchcpp
 {
 
-class Target;
 class Sound;
 
 struct SoundPrivate
@@ -22,7 +21,6 @@ struct SoundPrivate
         int sampleCount = 0;
         static IAudioOutput *audioOutput;
         std::shared_ptr<IAudioPlayer> player = nullptr;
-        Target *target = nullptr;
         const Sound *cloneRoot = nullptr;
 };
 
diff --git a/test/assets/asset_test.cpp b/test/assets/asset_test.cpp
index d08d56d6..5bd5920b 100644
--- a/test/assets/asset_test.cpp
+++ b/test/assets/asset_test.cpp
@@ -1,4 +1,5 @@
 #include <scratchcpp/asset.h>
+#include <scratchcpp/target.h>
 
 #include "../common.h"
 #include "testasset.h"
@@ -35,3 +36,13 @@ TEST(AssetTest, Data)
     ASSERT_EQ(asset.processedData, data);
     ASSERT_EQ(asset.callCount, 1);
 }
+
+TEST(AssetTest, Target)
+{
+    Asset asset("sound1", "a", "wav");
+    ASSERT_EQ(asset.target(), nullptr);
+
+    Target target;
+    asset.setTarget(&target);
+    ASSERT_EQ(asset.target(), &target);
+}
diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp
index f36b3e6b..d3b81bf2 100644
--- a/test/assets/sound_test.cpp
+++ b/test/assets/sound_test.cpp
@@ -145,16 +145,6 @@ TEST_F(SoundTest, IsPlaying)
     SoundPrivate::audioOutput = nullptr;
 }
 
-TEST_F(SoundTest, Target)
-{
-    Sound sound("sound1", "a", "wav");
-    ASSERT_EQ(sound.target(), nullptr);
-
-    Target target;
-    sound.setTarget(&target);
-    ASSERT_EQ(sound.target(), &target);
-}
-
 TEST_F(SoundTest, Clone)
 {
     auto sound = std::make_shared<Sound>("sound1", "a", "wav");

From 09c2b0d0eac87a01b09a9fbabb913b3f212f44b8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 13 Jan 2025 12:18:02 +0100
Subject: [PATCH 237/259] Add toRgba() method to Value

---
 include/scratchcpp/value.h           |  3 +
 include/scratchcpp/value_functions.h | 40 +++++++++++++
 src/scratch/value_functions.cpp      | 57 ++++++++++++++++++
 test/scratch_classes/value_test.cpp  | 90 ++++++++++++++++++++++++++++
 4 files changed, 190 insertions(+)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index 96c781f3..7f0c6a54 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -139,6 +139,9 @@ class LIBSCRATCHCPP_EXPORT Value
             return ret;
         }
 
+        /*! Converts the value to an RGBA quadruplet. */
+        Rgb toRgba() const { return value_toRgba(&m_data); }
+
         /*! Adds the given value to the value. */
         void add(const Value &v) { value_add(&m_data, &v.m_data, &m_data); }
 
diff --git a/include/scratchcpp/value_functions.h b/include/scratchcpp/value_functions.h
index 1542d2b9..2a8a2217 100644
--- a/include/scratchcpp/value_functions.h
+++ b/include/scratchcpp/value_functions.h
@@ -5,6 +5,45 @@
 namespace libscratchcpp
 {
 
+/*! A typedef for unsigned int. Holds the RGBA values. */
+using Rgb = unsigned int;
+
+/*! Returns the red component of the quadruplet rgb. */
+inline constexpr int red(Rgb rgb)
+{
+    return ((rgb >> 16) & 0xff);
+}
+
+/*! Returns the green component of the quadruplet rgb. */
+inline constexpr int green(Rgb rgb)
+{
+    return ((rgb >> 8) & 0xff);
+}
+
+/*! Returns the blue component of the quadruplet rgb. */
+inline constexpr int blue(Rgb rgb)
+{
+    return (rgb & 0xff);
+}
+
+/*! Returns the alpha component of the quadruplet rgb. */
+inline constexpr int alpha(Rgb rgb)
+{
+    return rgb >> 24;
+}
+
+/*! Creates an RGB triplet from the given color components. */
+inline constexpr Rgb rgb(int r, int g, int b)
+{
+    return (0xffu << 24) | ((r & 0xffu) << 16) | ((g & 0xffu) << 8) | (b & 0xffu);
+}
+
+/*! Creates an RGBA quadruplet from the given color components. */
+inline constexpr Rgb rgba(int r, int g, int b, int a)
+{
+    return ((a & 0xffu) << 24) | ((r & 0xffu) << 16) | ((g & 0xffu) << 8) | (b & 0xffu);
+}
+
 extern "C"
 {
     LIBSCRATCHCPP_EXPORT void value_free(ValueData *v);
@@ -33,6 +72,7 @@ extern "C"
     LIBSCRATCHCPP_EXPORT void value_toString(const ValueData *v, std::string *dst);
     LIBSCRATCHCPP_EXPORT char *value_toCString(const ValueData *v);
     LIBSCRATCHCPP_EXPORT void value_toUtf16(const ValueData *v, std::u16string *dst);
+    LIBSCRATCHCPP_EXPORT Rgb value_toRgba(const ValueData *v);
 
     LIBSCRATCHCPP_EXPORT bool value_doubleIsInt(double v);
 
diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp
index 0e157b65..7dced357 100644
--- a/src/scratch/value_functions.cpp
+++ b/src/scratch/value_functions.cpp
@@ -288,6 +288,63 @@ extern "C"
         dst->assign(utf8::utf8to16(s));
     }
 
+    /*! Returns the RGBA quadruplet from the given color value. */
+    Rgb value_toRgba(const ValueData *v)
+    {
+        // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L92-L103
+        char *string = nullptr;
+        size_t stringLen = 0;
+
+        if (v->type == ValueType::Number)
+            return v->numberValue;
+        else if (v->type == ValueType::String) {
+            string = value_toCString(v);
+            stringLen = strlen(string);
+        } else if (v->type == ValueType::Bool)
+            return v->boolValue;
+
+        if (stringLen > 0 && string[0] == '#') {
+            // https://github.com/scratchfoundation/scratch-vm/blob/a4f095db5e03e072ba222fe721eeeb543c9b9c15/src/util/color.js#L60-L69
+            // (this implementation avoids regex)
+
+            // Handle shorthand hex (e.g., "abc" -> "aabbcc")
+            char expandedHex[7] = { 0 };
+            char *ptr;
+
+            if (stringLen == 4) {
+                expandedHex[0] = string[1];
+                expandedHex[1] = string[1];
+                expandedHex[2] = string[2];
+                expandedHex[3] = string[2];
+                expandedHex[4] = string[3];
+                expandedHex[5] = string[3];
+                ptr = expandedHex;
+            } else if (stringLen == 7)
+                ptr = string + 1; // skip '#'
+            else {
+                free(string);
+                return rgb(0, 0, 0);
+            }
+
+            // Convert hex components to integers
+            int r, g, b;
+
+            if (std::sscanf(ptr, "%2x%2x%2x", &r, &g, &b) == 3) {
+                free(string);
+                return rgb(r, g, b);
+            }
+
+            free(string);
+        } else if (stringLen > 0) {
+            const double ret = value_stringToDouble(string);
+            free(string);
+            return ret;
+        } else if (string)
+            free(string);
+
+        return rgb(0, 0, 0);
+    }
+
     /*! Returns true if the given number represents a round integer. */
     bool value_doubleIsInt(double v)
     {
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 71e31bfd..76462ebe 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1616,6 +1616,60 @@ TEST(ValueTest, ToString)
         free(s);
 }
 
+TEST(ValueTest, ToRgba)
+{
+    Value v = "#ff0000";
+    ASSERT_EQ(v.toRgba(), rgba(255, 0, 0, 255));
+
+    v = "#00ff00";
+    ASSERT_EQ(v.toRgba(), rgba(0, 255, 0, 255));
+
+    v = "#0000ff";
+    ASSERT_EQ(v.toRgba(), rgba(0, 0, 255, 255));
+
+    v = "#800000";
+    ASSERT_EQ(v.toRgba(), rgba(128, 0, 0, 255));
+
+    v = "#008000";
+    ASSERT_EQ(v.toRgba(), rgba(0, 128, 0, 255));
+
+    v = "#000080";
+    ASSERT_EQ(v.toRgba(), rgba(0, 0, 128, 255));
+
+    v = "#FF0080FF";
+    ASSERT_EQ(v.toRgba(), rgba(0, 0, 0, 255));
+
+    v = "#abc";
+    ASSERT_EQ(v.toRgba(), rgba(170, 187, 204, 255));
+
+    v = "00ff00";
+    ASSERT_EQ(v.toRgba(), 0);
+
+    v = "123";
+    ASSERT_EQ(v.toRgba(), 123);
+
+    v = "#12345";
+    ASSERT_EQ(v.toRgba(), rgba(0, 0, 0, 255));
+
+    v = "";
+    ASSERT_EQ(v.toRgba(), rgba(0, 0, 0, 255));
+
+    v = "#gggggg";
+    ASSERT_EQ(v.toRgba(), rgba(0, 0, 0, 255));
+
+    v = 2164195583;
+    ASSERT_EQ(v.toRgba(), 2164195583);
+
+    v = 0;
+    ASSERT_EQ(v.toRgba(), 0);
+
+    v = true;
+    ASSERT_EQ(v.toRgba(), 1);
+
+    v = false;
+    ASSERT_EQ(v.toRgba(), 0);
+}
+
 TEST(ValueTest, AddFunction)
 {
     Value v = 50;
@@ -3121,3 +3175,39 @@ TEST(ValueTest, StringToBool)
     ASSERT_TRUE(value_stringToBool("0b100112001"));
     ASSERT_TRUE(value_stringToBool("0b10011001.1"));
 }
+
+TEST(ValueTest, RedComponent)
+{
+    Rgb color = rgba(255, 100, 50, 255);
+    ASSERT_EQ(red(color), 255);
+}
+
+TEST(ValueTest, GreenComponent)
+{
+    Rgb color = rgba(255, 100, 50, 255);
+    ASSERT_EQ(green(color), 100);
+}
+
+TEST(ValueTest, BlueComponent)
+{
+    Rgb color = rgba(255, 100, 50, 255);
+    ASSERT_EQ(blue(color), 50);
+}
+
+TEST(ValueTest, AlphaComponent)
+{
+    Rgb color = rgba(255, 100, 50, 128);
+    ASSERT_EQ(alpha(color), 128);
+}
+
+TEST(ValueTest, RGB)
+{
+    Rgb color = rgb(255, 100, 50);
+    ASSERT_EQ(color, 0xFFFF6432);
+}
+
+TEST(ValueTest, RGBA)
+{
+    Rgb color = rgba(255, 100, 50, 128);
+    ASSERT_EQ(color, 0x80FF6432);
+}

From a6069ae6779e531c5fb1f887814365cafbf8f85f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 13 Jan 2025 12:46:50 +0100
Subject: [PATCH 238/259] Use Rgb in touching methods

---
 include/scratchcpp/ispritehandler.h  |  4 ++--
 include/scratchcpp/istagehandler.h   |  4 ++--
 include/scratchcpp/sprite.h          |  4 ++--
 include/scratchcpp/stage.h           |  4 ++--
 include/scratchcpp/target.h          |  6 +++---
 src/blocks/sensingblocks.cpp         |  4 ++--
 src/scratch/sprite.cpp               |  4 ++--
 src/scratch/stage.cpp                |  4 ++--
 src/scratch/target.cpp               |  4 ++--
 test/blocks/sensing_blocks_test.cpp  | 16 ++++++++--------
 test/mocks/spritehandlermock.h       |  4 ++--
 test/mocks/stagehandlermock.h        |  4 ++--
 test/mocks/targetmock.h              |  4 ++--
 test/scratch_classes/sprite_test.cpp | 19 ++++++++++---------
 test/scratch_classes/stage_test.cpp  | 19 ++++++++++---------
 test/scratch_classes/target_test.cpp |  6 +++---
 16 files changed, 56 insertions(+), 54 deletions(-)

diff --git a/include/scratchcpp/ispritehandler.h b/include/scratchcpp/ispritehandler.h
index d8a61a68..e423f960 100644
--- a/include/scratchcpp/ispritehandler.h
+++ b/include/scratchcpp/ispritehandler.h
@@ -85,10 +85,10 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler
         virtual bool touchingPoint(double x, double y) const = 0;
 
         /*! Used to check whether the sprite touches the given color. */
-        virtual bool touchingColor(const Value &color) const = 0;
+        virtual bool touchingColor(Rgb color) const = 0;
 
         /*! Used to check whether the mask part of the sprite touches the given color. */
-        virtual bool touchingColor(const Value &color, const Value &mask) const = 0;
+        virtual bool touchingColor(Rgb color, Rgb mask) const = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/include/scratchcpp/istagehandler.h b/include/scratchcpp/istagehandler.h
index b6cf7f50..b417ec32 100644
--- a/include/scratchcpp/istagehandler.h
+++ b/include/scratchcpp/istagehandler.h
@@ -62,10 +62,10 @@ class LIBSCRATCHCPP_EXPORT IStageHandler
         virtual bool touchingPoint(double x, double y) const = 0;
 
         /*! Used to check whether the stage touches the given color. */
-        virtual bool touchingColor(const Value &color) const = 0;
+        virtual bool touchingColor(Rgb color) const = 0;
 
         /*! Used to check whether the mask part of the stage touches the given color. */
-        virtual bool touchingColor(const Value &color, const Value &mask) const = 0;
+        virtual bool touchingColor(Rgb color, Rgb mask) const = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h
index f2f7d517..e62f50c5 100644
--- a/include/scratchcpp/sprite.h
+++ b/include/scratchcpp/sprite.h
@@ -81,8 +81,8 @@ class LIBSCRATCHCPP_EXPORT Sprite
         void keepInFence(double newX, double newY, double *fencedX, double *fencedY) const;
 
         bool touchingPoint(double x, double y) const override;
-        bool touchingColor(const Value &color) const override;
-        bool touchingColor(const Value &color, const Value &mask) const override;
+        bool touchingColor(Rgb color) const override;
+        bool touchingColor(Rgb color, Rgb mask) const override;
 
         void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override;
 
diff --git a/include/scratchcpp/stage.h b/include/scratchcpp/stage.h
index 3a0df955..274c56ba 100644
--- a/include/scratchcpp/stage.h
+++ b/include/scratchcpp/stage.h
@@ -55,8 +55,8 @@ class LIBSCRATCHCPP_EXPORT Stage : public Target
         Rect fastBoundingRect() const override;
 
         bool touchingPoint(double x, double y) const override;
-        bool touchingColor(const Value &color) const override;
-        bool touchingColor(const Value &color, const Value &mask) const override;
+        bool touchingColor(Rgb color) const override;
+        bool touchingColor(Rgb color, Rgb mask) const override;
 
         void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override;
 
diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h
index 20d1a389..523b70b9 100644
--- a/include/scratchcpp/target.h
+++ b/include/scratchcpp/target.h
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <vector>
+#include <scratchcpp/value_functions.h>
 
 #include "drawable.h"
 #include "rect.h"
@@ -12,7 +13,6 @@ namespace libscratchcpp
 {
 
 class Variable;
-struct ValueData;
 class List;
 class Block;
 class Comment;
@@ -97,8 +97,8 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable
         bool touchingSprite(Sprite *sprite) const;
         virtual bool touchingPoint(double x, double y) const;
         bool touchingEdge() const;
-        virtual bool touchingColor(const Value &color) const;
-        virtual bool touchingColor(const Value &color, const Value &mask) const;
+        virtual bool touchingColor(Rgb color) const;
+        virtual bool touchingColor(Rgb color, Rgb mask) const;
 
         double graphicsEffectValue(IGraphicsEffect *effect) const;
         virtual void setGraphicsEffectValue(IGraphicsEffect *effect, double value);
diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp
index a1ef1c55..af1349ed 100644
--- a/src/blocks/sensingblocks.cpp
+++ b/src/blocks/sensingblocks.cpp
@@ -538,13 +538,13 @@ unsigned int SensingBlocks::touchingEdge(VirtualMachine *vm)
 
 unsigned int SensingBlocks::touchingColor(VirtualMachine *vm)
 {
-    vm->replaceReturnValue(vm->target()->touchingColor(*vm->getInput(0, 1)), 1);
+    vm->replaceReturnValue(vm->target()->touchingColor(vm->getInput(0, 1)->toRgba()), 1);
     return 0;
 }
 
 unsigned int SensingBlocks::colorIsTouchingColor(VirtualMachine *vm)
 {
-    vm->replaceReturnValue(vm->target()->touchingColor(*vm->getInput(0, 2), *vm->getInput(1, 2)), 2);
+    vm->replaceReturnValue(vm->target()->touchingColor(vm->getInput(0, 2)->toRgba(), vm->getInput(1, 2)->toRgba()), 2);
     return 1;
 }
 
diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index 9ca3347a..b755ca26 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -504,7 +504,7 @@ bool Sprite::touchingPoint(double x, double y) const
 }
 
 /*! Overrides Target#touchingColor(). */
-bool Sprite::touchingColor(const Value &color) const
+bool Sprite::touchingColor(Rgb color) const
 {
     if (!impl->iface)
         return false;
@@ -513,7 +513,7 @@ bool Sprite::touchingColor(const Value &color) const
 }
 
 /*! Overrides Target#touchingColor(). */
-bool Sprite::touchingColor(const Value &color, const Value &mask) const
+bool Sprite::touchingColor(Rgb color, Rgb mask) const
 {
     if (!impl->iface)
         return false;
diff --git a/src/scratch/stage.cpp b/src/scratch/stage.cpp
index 3a755886..124e6ff8 100644
--- a/src/scratch/stage.cpp
+++ b/src/scratch/stage.cpp
@@ -186,7 +186,7 @@ bool Stage::touchingPoint(double x, double y) const
 }
 
 /*! Overrides Target#touchingColor(). */
-bool Stage::touchingColor(const Value &color) const
+bool Stage::touchingColor(Rgb color) const
 {
     if (!impl->iface)
         return false;
@@ -195,7 +195,7 @@ bool Stage::touchingColor(const Value &color) const
 }
 
 /*! Overrides Target#touchingColor(). */
-bool Stage::touchingColor(const Value &color, const Value &mask) const
+bool Stage::touchingColor(Rgb color, Rgb mask) const
 {
     if (!impl->iface)
         return false;
diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index d1e4224f..4fc276aa 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -588,13 +588,13 @@ bool Target::touchingEdge() const
 }
 
 /*! Returns true if the Target is touching the given color (RGB triplet). */
-bool Target::touchingColor(const Value &color) const
+bool Target::touchingColor(Rgb color) const
 {
     return false;
 }
 
 /*! Returns true if the mask part of the Target is touching the given color (RGB triplet). */
-bool Target::touchingColor(const Value &color, const Value &mask) const
+bool Target::touchingColor(Rgb color, Rgb mask) const
 {
     return false;
 }
diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp
index ecb5588c..e486f372 100644
--- a/test/blocks/sensing_blocks_test.cpp
+++ b/test/blocks/sensing_blocks_test.cpp
@@ -422,21 +422,21 @@ TEST_F(SensingBlocksTest, TouchingColorImpl)
     vm.setFunctions(functions);
     vm.setConstValues(constValues);
 
-    EXPECT_CALL(target, touchingColor(constValues[0])).WillOnce(Return(false));
+    EXPECT_CALL(target, touchingColor(constValues[0].toRgba())).WillOnce(Return(false));
     vm.setBytecode(bytecode1);
     vm.run();
 
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_FALSE(vm.getInput(0, 1)->toBool());
 
-    EXPECT_CALL(target, touchingColor(constValues[0])).WillOnce(Return(true));
+    EXPECT_CALL(target, touchingColor(constValues[0].toRgba())).WillOnce(Return(true));
     vm.reset();
     vm.run();
 
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_TRUE(vm.getInput(0, 1)->toBool());
 
-    EXPECT_CALL(target, touchingColor(constValues[1])).WillOnce(Return(false));
+    EXPECT_CALL(target, touchingColor(constValues[1].toRgba())).WillOnce(Return(false));
     vm.reset();
     vm.setBytecode(bytecode2);
     vm.run();
@@ -444,7 +444,7 @@ TEST_F(SensingBlocksTest, TouchingColorImpl)
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_FALSE(vm.getInput(0, 1)->toBool());
 
-    EXPECT_CALL(target, touchingColor(constValues[1])).WillOnce(Return(true));
+    EXPECT_CALL(target, touchingColor(constValues[1].toRgba())).WillOnce(Return(true));
     vm.reset();
     vm.run();
 
@@ -495,21 +495,21 @@ TEST_F(SensingBlocksTest, ColorIsTouchingColorImpl)
     vm.setFunctions(functions);
     vm.setConstValues(constValues);
 
-    EXPECT_CALL(target, touchingColor(constValues[0], constValues[1])).WillOnce(Return(false));
+    EXPECT_CALL(target, touchingColor(constValues[0].toRgba(), constValues[1].toRgba())).WillOnce(Return(false));
     vm.setBytecode(bytecode1);
     vm.run();
 
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_FALSE(vm.getInput(0, 1)->toBool());
 
-    EXPECT_CALL(target, touchingColor(constValues[0], constValues[1])).WillOnce(Return(true));
+    EXPECT_CALL(target, touchingColor(constValues[0].toRgba(), constValues[1].toRgba())).WillOnce(Return(true));
     vm.reset();
     vm.run();
 
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_TRUE(vm.getInput(0, 1)->toBool());
 
-    EXPECT_CALL(target, touchingColor(constValues[2], constValues[3])).WillOnce(Return(false));
+    EXPECT_CALL(target, touchingColor(constValues[2].toRgba(), constValues[3].toRgba())).WillOnce(Return(false));
     vm.reset();
     vm.setBytecode(bytecode2);
     vm.run();
@@ -517,7 +517,7 @@ TEST_F(SensingBlocksTest, ColorIsTouchingColorImpl)
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_FALSE(vm.getInput(0, 1)->toBool());
 
-    EXPECT_CALL(target, touchingColor(constValues[2], constValues[3])).WillOnce(Return(true));
+    EXPECT_CALL(target, touchingColor(constValues[2].toRgba(), constValues[3].toRgba())).WillOnce(Return(true));
     vm.reset();
     vm.run();
 
diff --git a/test/mocks/spritehandlermock.h b/test/mocks/spritehandlermock.h
index a3d2ab4b..4a9055c0 100644
--- a/test/mocks/spritehandlermock.h
+++ b/test/mocks/spritehandlermock.h
@@ -35,6 +35,6 @@ class SpriteHandlerMock : public ISpriteHandler
 
         MOCK_METHOD(bool, touchingClones, (const std::vector<Sprite *> &), (const, override));
         MOCK_METHOD(bool, touchingPoint, (double, double), (const, override));
-        MOCK_METHOD(bool, touchingColor, (const Value &), (const, override));
-        MOCK_METHOD(bool, touchingColor, (const Value &, const Value &), (const, override));
+        MOCK_METHOD(bool, touchingColor, (Rgb), (const, override));
+        MOCK_METHOD(bool, touchingColor, (Rgb, Rgb), (const, override));
 };
diff --git a/test/mocks/stagehandlermock.h b/test/mocks/stagehandlermock.h
index 9e3892ed..1cc31205 100644
--- a/test/mocks/stagehandlermock.h
+++ b/test/mocks/stagehandlermock.h
@@ -26,6 +26,6 @@ class StageHandlerMock : public IStageHandler
 
         MOCK_METHOD(bool, touchingClones, (const std::vector<Sprite *> &), (const, override));
         MOCK_METHOD(bool, touchingPoint, (double, double), (const, override));
-        MOCK_METHOD(bool, touchingColor, (const Value &), (const, override));
-        MOCK_METHOD(bool, touchingColor, (const Value &, const Value &), (const, override));
+        MOCK_METHOD(bool, touchingColor, (Rgb), (const, override));
+        MOCK_METHOD(bool, touchingColor, (Rgb, Rgb), (const, override));
 };
diff --git a/test/mocks/targetmock.h b/test/mocks/targetmock.h
index aca1c4c6..bfa62420 100644
--- a/test/mocks/targetmock.h
+++ b/test/mocks/targetmock.h
@@ -27,8 +27,8 @@ class TargetMock : public Target
 
         MOCK_METHOD(bool, touchingPoint, (double, double), (const, override));
 
-        MOCK_METHOD(bool, touchingColor, (const Value &), (const, override));
-        MOCK_METHOD(bool, touchingColor, (const Value &, const Value &), (const, override));
+        MOCK_METHOD(bool, touchingColor, (Rgb), (const, override));
+        MOCK_METHOD(bool, touchingColor, (Rgb, Rgb), (const, override));
 
         MOCK_METHOD(void, setGraphicsEffectValue, (IGraphicsEffect *, double), (override));
         MOCK_METHOD(void, clearGraphicsEffects, (), (override));
diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp
index 572bd5f5..5749896a 100644
--- a/test/scratch_classes/sprite_test.cpp
+++ b/test/scratch_classes/sprite_test.cpp
@@ -834,18 +834,19 @@ TEST(SpriteTest, TouchingColor)
     EXPECT_CALL(iface, init);
     sprite.setInterface(&iface);
 
-    Value v1 = 4278228630, v2 = "#FF00FA";
-    EXPECT_CALL(iface, touchingColor(v1)).WillOnce(Return(false));
-    ASSERT_FALSE(sprite.touchingColor(v1));
+    Rgb c1 = Value(4278228630).toRgba();
+    Rgb c2 = Value("#FF00FA").toRgba();
+    EXPECT_CALL(iface, touchingColor(c1)).WillOnce(Return(false));
+    ASSERT_FALSE(sprite.touchingColor(c1));
 
-    EXPECT_CALL(iface, touchingColor(v2)).WillOnce(Return(true));
-    ASSERT_TRUE(sprite.touchingColor(v2));
+    EXPECT_CALL(iface, touchingColor(c2)).WillOnce(Return(true));
+    ASSERT_TRUE(sprite.touchingColor(c2));
 
-    EXPECT_CALL(iface, touchingColor(v1, v2)).WillOnce(Return(false));
-    ASSERT_FALSE(sprite.touchingColor(v1, v2));
+    EXPECT_CALL(iface, touchingColor(c1, c2)).WillOnce(Return(false));
+    ASSERT_FALSE(sprite.touchingColor(c1, c2));
 
-    EXPECT_CALL(iface, touchingColor(v2, v1)).WillOnce(Return(true));
-    ASSERT_TRUE(sprite.touchingColor(v2, v1));
+    EXPECT_CALL(iface, touchingColor(c2, c1)).WillOnce(Return(true));
+    ASSERT_TRUE(sprite.touchingColor(c2, c1));
 }
 
 TEST(SpriteTest, GraphicsEffects)
diff --git a/test/scratch_classes/stage_test.cpp b/test/scratch_classes/stage_test.cpp
index 53f0477d..bdf7c4c8 100644
--- a/test/scratch_classes/stage_test.cpp
+++ b/test/scratch_classes/stage_test.cpp
@@ -229,18 +229,19 @@ TEST(StageTest, TouchingColor)
     EXPECT_CALL(iface, init);
     stage.setInterface(&iface);
 
-    Value v1 = 4278228630, v2 = "#FF00FA";
-    EXPECT_CALL(iface, touchingColor(v1)).WillOnce(Return(false));
-    ASSERT_FALSE(stage.touchingColor(v1));
+    Rgb c1 = Value(4278228630).toRgba();
+    Rgb c2 = Value("#FF00FA").toRgba();
+    EXPECT_CALL(iface, touchingColor(c1)).WillOnce(Return(false));
+    ASSERT_FALSE(stage.touchingColor(c1));
 
-    EXPECT_CALL(iface, touchingColor(v2)).WillOnce(Return(true));
-    ASSERT_TRUE(stage.touchingColor(v2));
+    EXPECT_CALL(iface, touchingColor(c2)).WillOnce(Return(true));
+    ASSERT_TRUE(stage.touchingColor(c2));
 
-    EXPECT_CALL(iface, touchingColor(v1, v2)).WillOnce(Return(false));
-    ASSERT_FALSE(stage.touchingColor(v1, v2));
+    EXPECT_CALL(iface, touchingColor(c1, c2)).WillOnce(Return(false));
+    ASSERT_FALSE(stage.touchingColor(c1, c2));
 
-    EXPECT_CALL(iface, touchingColor(v2, v1)).WillOnce(Return(true));
-    ASSERT_TRUE(stage.touchingColor(v2, v1));
+    EXPECT_CALL(iface, touchingColor(c2, c1)).WillOnce(Return(true));
+    ASSERT_TRUE(stage.touchingColor(c2, c1));
 }
 
 TEST(StageTest, GraphicsEffects)
diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index 93ef3330..92596c01 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -759,9 +759,9 @@ TEST(TargetTest, TouchingEdge)
 TEST(TargetTest, TouchingColor)
 {
     Target target;
-    Value v;
-    ASSERT_FALSE(target.touchingColor(v));
-    ASSERT_FALSE(target.touchingColor(v, v));
+    Rgb c = 0;
+    ASSERT_FALSE(target.touchingColor(c));
+    ASSERT_FALSE(target.touchingColor(c, c));
 }
 
 TEST(TargetTest, GraphicsEffects)

From 89f44259f04e2b7c506bb20185ffd8305af0f08a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 14 Jan 2025 15:06:46 +0100
Subject: [PATCH 239/259] LLVMCodeBuilder: Do not recreate var pointers when
 vars are used

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index e300f705..74a56115 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1251,7 +1251,9 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
     LLVMInstruction ins(LLVMInstruction::Type::ReadVariable);
     ins.workVariable = variable;
-    m_variablePtrs[variable] = LLVMVariablePtr();
+
+    if (m_variablePtrs.find(variable) == m_variablePtrs.cend())
+        m_variablePtrs[variable] = LLVMVariablePtr();
 
     auto ret = std::make_shared<LLVMRegister>(Compiler::StaticType::Unknown);
     ret->isRawValue = false;
@@ -1506,8 +1508,10 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val
 {
     LLVMInstruction ins(LLVMInstruction::Type::WriteVariable);
     ins.workVariable = variable;
-    m_variablePtrs[variable] = LLVMVariablePtr();
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value });
+
+    if (m_variablePtrs.find(variable) == m_variablePtrs.cend())
+        m_variablePtrs[variable] = LLVMVariablePtr();
 }
 
 void LLVMCodeBuilder::createListClear(List *list)

From 1fa99e70731e826633c654de2d9ea8e69667c337 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 14 Jan 2025 15:10:33 +0100
Subject: [PATCH 240/259] LLVMCodeBuilder: Do not recreate list pointers when
 lists are used

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 44 ++++++++++++++-----
 1 file changed, 34 insertions(+), 10 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 74a56115..fec1b92c 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1267,7 +1267,10 @@ CompilerValue *LLVMCodeBuilder::addListContents(List *list)
 {
     LLVMInstruction ins(LLVMInstruction::Type::GetListContents);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
+
     return createOp(ins, Compiler::StaticType::String);
 }
 
@@ -1275,7 +1278,9 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
 {
     LLVMInstruction ins(LLVMInstruction::Type::GetListItem);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
 
     ins.args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(index) });
 
@@ -1291,7 +1296,10 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item
 {
     LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
+
     return createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item });
 }
 
@@ -1299,7 +1307,10 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
 {
     LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
+
     return createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item });
 }
 
@@ -1307,7 +1318,10 @@ CompilerValue *LLVMCodeBuilder::addListSize(List *list)
 {
     LLVMInstruction ins(LLVMInstruction::Type::GetListSize);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
+
     return createOp(ins, Compiler::StaticType::Number);
 }
 
@@ -1518,40 +1532,50 @@ void LLVMCodeBuilder::createListClear(List *list)
 {
     LLVMInstruction ins(LLVMInstruction::Type::ClearList);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
     createOp(ins, Compiler::StaticType::Void);
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
 }
 
 void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index)
 {
     LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index });
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
 }
 
 void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
 {
     LLVMInstruction ins(LLVMInstruction::Type::AppendToList);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item });
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
 }
 
 void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item)
 {
     LLVMInstruction ins(LLVMInstruction::Type::InsertToList);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
     createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
 }
 
 void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item)
 {
     LLVMInstruction ins(LLVMInstruction::Type::ListReplace);
     ins.workList = list;
-    m_listPtrs[list] = LLVMListPtr();
     createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
+
+    if (m_listPtrs.find(list) == m_listPtrs.cend())
+        m_listPtrs[list] = LLVMListPtr();
 }
 
 void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond)

From 6727c57c743d7a8084b26de43ab12d3501d616ca Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 16 Jan 2025 23:05:28 +0100
Subject: [PATCH 241/259] LLVMCodeBuilder: Add loop scopes

---
 src/dev/engine/internal/llvm/CMakeLists.txt   |   1 +
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 159 ++++++++++++------
 .../engine/internal/llvm/llvmcodebuilder.h    |  12 +-
 .../engine/internal/llvm/llvminstruction.h    |   7 +-
 src/dev/engine/internal/llvm/llvmloopscope.h  |  18 ++
 5 files changed, 145 insertions(+), 52 deletions(-)
 create mode 100644 src/dev/engine/internal/llvm/llvmloopscope.h

diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt
index e1bb3ef5..38b7e4d2 100644
--- a/src/dev/engine/internal/llvm/CMakeLists.txt
+++ b/src/dev/engine/internal/llvm/CMakeLists.txt
@@ -16,6 +16,7 @@ target_sources(scratchcpp
     llvmtypes.cpp
     llvmtypes.h
     llvmfunctions.cpp
+    llvmloopscope.h
     llvmcompilercontext.cpp
     llvmcompilercontext.h
     llvmexecutablecode.cpp
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index fec1b92c..0d52e115 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -16,6 +16,7 @@
 #include "llvmifstatement.h"
 #include "llvmloop.h"
 #include "llvmtypes.h"
+#include "llvmloopscope.h"
 
 using namespace libscratchcpp;
 
@@ -44,8 +45,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 {
     if (!m_warp) {
         // Do not create coroutine if there are no yield instructions nor non-warp procedure calls
-        auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const LLVMInstruction &step) {
-            return step.type == LLVMInstruction::Type::Yield || (step.type == LLVMInstruction::Type::CallProcedure && step.procedurePrototype && !step.procedurePrototype->warp());
+        auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const std::shared_ptr<LLVMInstruction> &step) {
+            return step->type == LLVMInstruction::Type::Yield || (step->type == LLVMInstruction::Type::CallProcedure && step->procedurePrototype && !step->procedurePrototype->warp());
         });
 
         if (it == m_instructions.end())
@@ -122,12 +123,16 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
         m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
     }
 
+    assert(m_loopScope == -1);
+    m_loopScope = -1;
     m_scopeVariables.clear();
     m_scopeLists.clear();
     pushScopeLevel();
 
     // Execute recorded steps
-    for (const LLVMInstruction &step : m_instructions) {
+    for (const auto insPtr : m_instructions) {
+        const LLVMInstruction &step = *insPtr;
+
         switch (step.type) {
             case LLVMInstruction::Type::FunctionCall: {
                 std::vector<llvm::Type *> types;
@@ -1007,6 +1012,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 loops.push_back(loop);
                 pushScopeLevel();
+                pushLoopScope(true);
                 break;
             }
 
@@ -1036,6 +1042,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // Switch to body branch
                 m_builder.SetInsertPoint(body);
                 pushScopeLevel();
+                pushLoopScope(true);
                 break;
             }
 
@@ -1057,6 +1064,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 // Switch to body branch
                 m_builder.SetInsertPoint(body);
                 pushScopeLevel();
+                pushLoopScope(true);
                 break;
             }
 
@@ -1090,6 +1098,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
                 loops.pop_back();
                 popScopeLevel();
+                popLoopScope();
                 break;
             }
 
@@ -1198,16 +1207,16 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName,
 {
     assert(argTypes.size() == args.size());
 
-    LLVMInstruction ins(LLVMInstruction::Type::FunctionCall);
-    ins.functionName = functionName;
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::FunctionCall, currentLoopScope());
+    ins->functionName = functionName;
 
     for (size_t i = 0; i < args.size(); i++)
-        ins.args.push_back({ argTypes[i], static_cast<LLVMRegister *>(args[i]) });
+        ins->args.push_back({ argTypes[i], static_cast<LLVMRegister *>(args[i]) });
 
     if (returnType != Compiler::StaticType::Void) {
         auto reg = std::make_shared<LLVMRegister>(returnType);
         reg->isRawValue = true;
-        ins.functionReturnReg = reg.get();
+        ins->functionReturnReg = reg.get();
         m_instructions.push_back(ins);
         return addReg(reg);
     }
@@ -1219,14 +1228,14 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName,
 CompilerValue *LLVMCodeBuilder::addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
 {
     CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args);
-    m_instructions.back().functionTargetArg = true;
+    m_instructions.back()->functionTargetArg = true;
     return ret;
 }
 
 CompilerValue *LLVMCodeBuilder::addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
 {
     CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args);
-    m_instructions.back().functionCtxArg = true;
+    m_instructions.back()->functionCtxArg = true;
     return ret;
 }
 
@@ -1249,15 +1258,15 @@ CompilerValue *LLVMCodeBuilder::addLocalVariableValue(CompilerLocalVariable *var
 
 CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::ReadVariable);
-    ins.workVariable = variable;
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::ReadVariable, currentLoopScope());
+    ins->workVariable = variable;
 
     if (m_variablePtrs.find(variable) == m_variablePtrs.cend())
         m_variablePtrs[variable] = LLVMVariablePtr();
 
     auto ret = std::make_shared<LLVMRegister>(Compiler::StaticType::Unknown);
     ret->isRawValue = false;
-    ins.functionReturnReg = ret.get();
+    ins->functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
     return addReg(ret);
@@ -1265,7 +1274,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 
 CompilerValue *LLVMCodeBuilder::addListContents(List *list)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::GetListContents);
+    LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope());
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1276,17 +1285,17 @@ CompilerValue *LLVMCodeBuilder::addListContents(List *list)
 
 CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::GetListItem);
-    ins.workList = list;
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::GetListItem, currentLoopScope());
+    ins->workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
 
-    ins.args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(index) });
+    ins->args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(index) });
 
     auto ret = std::make_shared<LLVMRegister>(Compiler::StaticType::Unknown);
     ret->isRawValue = false;
-    ins.functionReturnReg = ret.get();
+    ins->functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
     return addReg(ret);
@@ -1294,7 +1303,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
 
 CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex);
+    LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope());
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1305,7 +1314,7 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item
 
 CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem);
+    LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope());
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1316,7 +1325,7 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
 
 CompilerValue *LLVMCodeBuilder::addListSize(List *list)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::GetListSize);
+    LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope());
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1340,11 +1349,11 @@ CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name)
 
     const auto index = it - argNames.begin();
     const Compiler::StaticType type = getProcedureArgType(m_procedurePrototype->argumentTypes()[index]);
-    LLVMInstruction ins(LLVMInstruction::Type::ProcedureArg);
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::ProcedureArg, currentLoopScope());
     auto ret = std::make_shared<LLVMRegister>(type);
     ret->isRawValue = (type != Compiler::StaticType::Unknown);
-    ins.functionReturnReg = ret.get();
-    ins.procedureArgIndex = index;
+    ins->functionReturnReg = ret.get();
+    ins->procedureArgIndex = index;
 
     m_instructions.push_back(ins);
     return addReg(ret);
@@ -1520,7 +1529,7 @@ void LLVMCodeBuilder::createLocalVariableWrite(CompilerLocalVariable *variable,
 
 void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::WriteVariable);
+    LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope());
     ins.workVariable = variable;
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value });
 
@@ -1530,7 +1539,7 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val
 
 void LLVMCodeBuilder::createListClear(List *list)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::ClearList);
+    LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope());
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void);
 
@@ -1540,7 +1549,7 @@ void LLVMCodeBuilder::createListClear(List *list)
 
 void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem);
+    LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope());
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index });
 
@@ -1550,7 +1559,7 @@ void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index)
 
 void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::AppendToList);
+    LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope());
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item });
 
@@ -1560,7 +1569,7 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
 
 void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::InsertToList);
+    LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope());
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
 
@@ -1570,7 +1579,7 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile
 
 void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::ListReplace);
+    LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope());
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
 
@@ -1580,63 +1589,70 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil
 
 void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::BeginIf);
-    ins.args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginIf, currentLoopScope());
+    ins->args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::beginElseBranch()
 {
-    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::BeginElse));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginElse, currentLoopScope()));
 }
 
 void LLVMCodeBuilder::endIf()
 {
-    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndIf));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::EndIf, currentLoopScope()));
 }
 
 void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatLoop);
-    ins.args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(count) });
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope());
+    ins->args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(count) });
     m_instructions.push_back(ins);
+    pushLoopScope(false);
 }
 
 void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::BeginWhileLoop);
-    ins.args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope());
+    ins->args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
+    pushLoopScope(false);
 }
 
 void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::BeginRepeatUntilLoop);
-    ins.args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope());
+    ins->args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
+    pushLoopScope(false);
 }
 
 void LLVMCodeBuilder::beginLoopCondition()
 {
-    m_instructions.push_back({ LLVMInstruction::Type::BeginLoopCondition });
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope()));
 }
 
 void LLVMCodeBuilder::endLoop()
 {
     if (!m_warp)
-        m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::Yield));
+        m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Yield, currentLoopScope()));
 
-    m_instructions.push_back(LLVMInstruction(LLVMInstruction::Type::EndLoop));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::EndLoop, currentLoopScope()));
+    popLoopScope();
 }
 
 void LLVMCodeBuilder::yield()
 {
-    m_instructions.push_back({ LLVMInstruction::Type::Yield });
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Yield, currentLoopScope()));
+
+    if (m_loopScope >= 0)
+        m_loopScopes[m_loopScope]->containsYield = true;
 }
 
 void LLVMCodeBuilder::createStop()
 {
-    m_instructions.push_back({ LLVMInstruction::Type::Stop });
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Stop, currentLoopScope()));
 }
 
 void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args)
@@ -1649,7 +1665,7 @@ void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compi
     for (BlockPrototype::ArgType type : procedureArgs)
         types.push_back(getProcedureArgType(type));
 
-    LLVMInstruction ins(LLVMInstruction::Type::CallProcedure);
+    LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope());
     ins.procedurePrototype = prototype;
     createOp(ins, Compiler::StaticType::Void, types, args);
 }
@@ -1762,6 +1778,36 @@ void LLVMCodeBuilder::popScopeLevel()
     m_heap.pop_back();
 }
 
+void LLVMCodeBuilder::pushLoopScope(bool buildPhase)
+{
+    if (buildPhase)
+        m_loopScope = m_loopScopeCounter++;
+    else {
+        auto scope = std::make_shared<LLVMLoopScope>();
+        m_loopScopes.push_back(scope);
+
+        if (m_loopScope >= 0) {
+            auto currentScope = m_loopScopes[m_loopScope];
+            currentScope->childScopes.push_back(scope);
+            scope->parentScope = currentScope;
+        }
+
+        m_loopScope = m_loopScopes.size() - 1;
+    }
+
+    m_loopScopeTree.push_back(m_loopScope);
+}
+
+void LLVMCodeBuilder::popLoopScope()
+{
+    m_loopScopeTree.pop_back();
+
+    if (m_loopScopeTree.empty()) {
+        m_loopScope = -1;
+    } else
+        m_loopScope = m_loopScopeTree.back();
+}
+
 std::string LLVMCodeBuilder::getMainFunctionName(BlockPrototype *procedurePrototype)
 {
     return procedurePrototype ? "proc." + procedurePrototype->procCode() : "script";
@@ -2147,6 +2193,16 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr)
     m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
 }
 
+LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
+{
+    return createOp({ type, currentLoopScope() }, retType, argType, args);
+}
+
+LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
+{
+    return createOp({ type, currentLoopScope() }, retType, argTypes, args);
+}
+
 LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
 {
     std::vector<Compiler::StaticType> types;
@@ -2160,22 +2216,27 @@ LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::St
 
 LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
 {
-    m_instructions.push_back(ins);
-    LLVMInstruction &createdIns = m_instructions.back();
+    auto createdIns = std::make_shared<LLVMInstruction>(ins);
+    m_instructions.push_back(createdIns);
 
     for (size_t i = 0; i < args.size(); i++)
-        createdIns.args.push_back({ argTypes[i], static_cast<LLVMRegister *>(args[i]) });
+        createdIns->args.push_back({ argTypes[i], static_cast<LLVMRegister *>(args[i]) });
 
     if (retType != Compiler::StaticType::Void) {
         auto ret = std::make_shared<LLVMRegister>(retType);
         ret->isRawValue = true;
-        createdIns.functionReturnReg = ret.get();
+        createdIns->functionReturnReg = ret.get();
         return addReg(ret);
     }
 
     return nullptr;
 }
 
+std::shared_ptr<LLVMLoopScope> LLVMCodeBuilder::currentLoopScope() const
+{
+    return m_loopScope >= 0 ? m_loopScopes[m_loopScope] : nullptr;
+}
+
 void LLVMCodeBuilder::createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType)
 {
     llvm::Value *converted = nullptr;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index f6a6142e..bd5affe1 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -20,6 +20,7 @@ namespace libscratchcpp
 
 class LLVMCompilerContext;
 class LLVMConstantRegister;
+class LLVMLoopScope;
 
 class LLVMCodeBuilder : public ICodeBuilder
 {
@@ -119,6 +120,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         void createListMap();
         void pushScopeLevel();
         void popScopeLevel();
+        void pushLoopScope(bool buildPhase);
+        void popLoopScope();
 
         std::string getMainFunctionName(BlockPrototype *procedurePrototype);
         std::string getResumeFunctionName(BlockPrototype *procedurePrototype);
@@ -147,8 +150,11 @@ class LLVMCodeBuilder : public ICodeBuilder
         void reloadLists();
         void updateListDataPtr(const LLVMListPtr &listPtr);
 
+        LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
+        LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
         LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
         LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
+        std::shared_ptr<LLVMLoopScope> currentLoopScope() const;
 
         void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
         void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType);
@@ -215,7 +221,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::StructType *m_valueDataType = nullptr;
         llvm::FunctionType *m_resumeFuncType = nullptr;
 
-        std::vector<LLVMInstruction> m_instructions;
+        std::vector<std::shared_ptr<LLVMInstruction>> m_instructions;
         std::vector<std::shared_ptr<LLVMRegister>> m_regs;
         std::vector<std::shared_ptr<CompilerLocalVariable>> m_localVars;
         BlockPrototype *m_procedurePrototype = nullptr;
@@ -223,6 +229,10 @@ class LLVMCodeBuilder : public ICodeBuilder
         bool m_warp = false;
         int m_defaultArgCount = 0;
 
+        long m_loopScope = -1; // index
+        std::vector<std::shared_ptr<LLVMLoopScope>> m_loopScopes;
+        long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase
+        std::vector<long> m_loopScopeTree;
         std::vector<std::vector<llvm::Value *>> m_heap; // scopes
 
         std::shared_ptr<ExecutableCode> m_output;
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index ddb39677..4f1787f5 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -10,6 +10,7 @@ namespace libscratchcpp
 {
 
 class BlockPrototype;
+class LLVMLoopScope;
 
 struct LLVMInstruction
 {
@@ -77,8 +78,9 @@ struct LLVMInstruction
             ProcedureArg
         };
 
-        LLVMInstruction(Type type) :
-            type(type)
+        LLVMInstruction(Type type, std::shared_ptr<LLVMLoopScope> loopScope) :
+            type(type),
+            loopScope(loopScope)
         {
         }
 
@@ -92,6 +94,7 @@ struct LLVMInstruction
         List *workList = nullptr;         // for lists
         BlockPrototype *procedurePrototype = nullptr;
         size_t procedureArgIndex = 0;
+        std::shared_ptr<LLVMLoopScope> loopScope;
 };
 
 } // namespace libscratchcpp
diff --git a/src/dev/engine/internal/llvm/llvmloopscope.h b/src/dev/engine/internal/llvm/llvmloopscope.h
new file mode 100644
index 00000000..859c2cb1
--- /dev/null
+++ b/src/dev/engine/internal/llvm/llvmloopscope.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <vector>
+#include <memory>
+
+namespace libscratchcpp
+{
+
+struct LLVMLoopScope
+{
+        bool containsYield = false;
+        std::shared_ptr<LLVMLoopScope> parentScope;
+        std::vector<std::shared_ptr<LLVMLoopScope>> childScopes;
+};
+
+} // namespace libscratchcpp

From df3c103835046aa8b44143fca365e5244427432e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 16 Jan 2025 23:13:17 +0100
Subject: [PATCH 242/259] LLVMCodeBuilder: Store source instruction in
 registers

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 15 ++++++++-------
 src/dev/engine/internal/llvm/llvmcodebuilder.h   |  2 +-
 src/dev/engine/internal/llvm/llvmregisterbase.h  |  3 +++
 3 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 0d52e115..4e241b0c 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1218,7 +1218,7 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName,
         reg->isRawValue = true;
         ins->functionReturnReg = reg.get();
         m_instructions.push_back(ins);
-        return addReg(reg);
+        return addReg(reg, ins);
     }
 
     m_instructions.push_back(ins);
@@ -1243,7 +1243,7 @@ CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value)
 {
     auto constReg = std::make_shared<LLVMConstantRegister>(TYPE_MAP[value.type()], value);
     auto reg = std::reinterpret_pointer_cast<LLVMRegister>(constReg);
-    return static_cast<CompilerConstant *>(static_cast<CompilerValue *>(addReg(reg)));
+    return static_cast<CompilerConstant *>(static_cast<CompilerValue *>(addReg(reg, nullptr)));
 }
 
 CompilerValue *LLVMCodeBuilder::addLoopIndex()
@@ -1269,7 +1269,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
     ins->functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
-    return addReg(ret);
+    return addReg(ret, ins);
 }
 
 CompilerValue *LLVMCodeBuilder::addListContents(List *list)
@@ -1298,7 +1298,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
     ins->functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
-    return addReg(ret);
+    return addReg(ret, ins);
 }
 
 CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item)
@@ -1356,7 +1356,7 @@ CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name)
     ins->procedureArgIndex = index;
 
     m_instructions.push_back(ins);
-    return addReg(ret);
+    return addReg(ret, ins);
 }
 
 CompilerValue *LLVMCodeBuilder::createAdd(CompilerValue *operand1, CompilerValue *operand2)
@@ -1857,8 +1857,9 @@ void LLVMCodeBuilder::verifyFunction(llvm::Function *func)
     }
 }
 
-LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg)
+LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr<LLVMRegister> reg, std::shared_ptr<LLVMInstruction> ins)
 {
+    reg->instruction = ins;
     m_regs.push_back(reg);
     return reg.get();
 }
@@ -2226,7 +2227,7 @@ LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::St
         auto ret = std::make_shared<LLVMRegister>(retType);
         ret->isRawValue = true;
         createdIns->functionReturnReg = ret.get();
-        return addReg(ret);
+        return addReg(ret, createdIns);
     }
 
     return nullptr;
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index bd5affe1..0cec7fb9 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -129,7 +129,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Function *getOrCreateFunction(const std::string &name, llvm::FunctionType *type);
         void verifyFunction(llvm::Function *func);
 
-        LLVMRegister *addReg(std::shared_ptr<LLVMRegister> reg);
+        LLVMRegister *addReg(std::shared_ptr<LLVMRegister> reg, std::shared_ptr<LLVMInstruction> ins);
 
         llvm::Value *addAlloca(llvm::Type *type);
         void freeLater(llvm::Value *value);
diff --git a/src/dev/engine/internal/llvm/llvmregisterbase.h b/src/dev/engine/internal/llvm/llvmregisterbase.h
index 2267a6df..33aace03 100644
--- a/src/dev/engine/internal/llvm/llvmregisterbase.h
+++ b/src/dev/engine/internal/llvm/llvmregisterbase.h
@@ -15,12 +15,15 @@ class Value;
 namespace libscratchcpp
 {
 
+class LLVMInstruction;
+
 struct LLVMRegisterBase
 {
         virtual const Value &constValue() const = 0;
 
         llvm::Value *value = nullptr;
         bool isRawValue = false;
+        std::shared_ptr<LLVMInstruction> instruction;
 };
 
 } // namespace libscratchcpp

From fa9de140766f216a34065aa47d303e9094c0f49d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 16 Jan 2025 23:16:42 +0100
Subject: [PATCH 243/259] LLVMCodeBuilder: Store variable instructions

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 3 +++
 src/dev/engine/internal/llvm/llvmcodebuilder.h   | 1 +
 2 files changed, 4 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 4e241b0c..132472ed 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1269,6 +1269,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
     ins->functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
+    m_variableInstructions.push_back(m_instructions.back());
     return addReg(ret, ins);
 }
 
@@ -1535,6 +1536,8 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val
 
     if (m_variablePtrs.find(variable) == m_variablePtrs.cend())
         m_variablePtrs[variable] = LLVMVariablePtr();
+
+    m_variableInstructions.push_back(m_instructions.back());
 }
 
 void LLVMCodeBuilder::createListClear(List *list)
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 0cec7fb9..4a34ffd2 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -233,6 +233,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         std::vector<std::shared_ptr<LLVMLoopScope>> m_loopScopes;
         long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase
         std::vector<long> m_loopScopeTree;
+        std::vector<std::shared_ptr<LLVMInstruction>> m_variableInstructions;
         std::vector<std::vector<llvm::Value *>> m_heap; // scopes
 
         std::shared_ptr<ExecutableCode> m_output;

From 469c3e8af90461687c82003baaa7bcf3bacae07c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 16 Jan 2025 23:19:22 +0100
Subject: [PATCH 244/259] LLVMCodeBuilder: Initialize variable stack copies
 outside loops

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 132472ed..e6d5cf82 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -106,7 +106,18 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
         // All variables are currently created on the stack and synced later (seems to be faster)
         // NOTE: Strings are NOT copied, only the pointer and string size are copied
         varPtr.stackPtr = m_builder.CreateAlloca(m_valueDataType);
-        varPtr.onStack = false; // use heap before the first assignment
+
+        // If there are no write operations outside loops, initialize the stack variable now
+        Variable *variable = var;
+        auto it = std::find_if(m_variableInstructions.begin(), m_variableInstructions.end(), [variable](const std::shared_ptr<LLVMInstruction> &ins) {
+            return ins->type == LLVMInstruction::Type::WriteVariable && ins->workVariable == variable && !ins->loopScope;
+        });
+
+        if (it == m_variableInstructions.end()) {
+            createValueCopy(ptr, varPtr.stackPtr);
+            varPtr.onStack = true;
+        } else
+            varPtr.onStack = false; // use heap before the first assignment
     }
 
     // Create list pointers

From 9f5f45ce0eb756d1ede89a718679f715993877ca Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 16 Jan 2025 23:22:19 +0100
Subject: [PATCH 245/259] LLVMCodeBuilder: Store loop variable write
 instructions

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 5 +++++
 src/dev/engine/internal/llvm/llvmvariableptr.h   | 7 +++++++
 2 files changed, 12 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index e6d5cf82..491c3036 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1548,6 +1548,11 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val
     if (m_variablePtrs.find(variable) == m_variablePtrs.cend())
         m_variablePtrs[variable] = LLVMVariablePtr();
 
+    if (m_loopScope >= 0) {
+        auto scope = m_loopScopes[m_loopScope];
+        m_variablePtrs[variable].loopVariableWrites[scope].push_back(m_instructions.back());
+    }
+
     m_variableInstructions.push_back(m_instructions.back());
 }
 
diff --git a/src/dev/engine/internal/llvm/llvmvariableptr.h b/src/dev/engine/internal/llvm/llvmvariableptr.h
index c2525dfd..28105f19 100644
--- a/src/dev/engine/internal/llvm/llvmvariableptr.h
+++ b/src/dev/engine/internal/llvm/llvmvariableptr.h
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <scratchcpp/dev/compiler.h>
+#include <unordered_map>
 
 namespace llvm
 {
@@ -14,6 +15,9 @@ class Value;
 namespace libscratchcpp
 {
 
+class LLVMLoopScope;
+class LLVMInstruction;
+
 struct LLVMVariablePtr
 {
         llvm::Value *stackPtr = nullptr;
@@ -21,6 +25,9 @@ struct LLVMVariablePtr
         Compiler::StaticType type = Compiler::StaticType::Unknown;
         bool onStack = false;
         bool changed = false;
+
+        // Used in build phase to check the type safety of variables in loops
+        std::unordered_map<std::shared_ptr<LLVMLoopScope>, std::vector<std::shared_ptr<LLVMInstruction>>> loopVariableWrites; // loop scope, write instructions
 };
 
 } // namespace libscratchcpp

From f6e001345080cddb3fce047d6ac6f1130b92bdf7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 17 Jan 2025 00:19:09 +0100
Subject: [PATCH 246/259] LLVMCodeBuilder: Implement loop type analysis for
 variables

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 173 ++++-
 .../engine/internal/llvm/llvmcodebuilder.h    |   5 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 672 ++++++++++++++++++
 3 files changed, 847 insertions(+), 3 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 491c3036..d674bc44 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -632,6 +632,8 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
                 varPtr.changed = true;
 
+                const bool safe = isVariableTypeSafe(insPtr, varPtr.type);
+
                 // Initialize stack variable on first assignment
                 if (!varPtr.onStack) {
                     varPtr.onStack = true;
@@ -652,6 +654,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     m_builder.CreateStore(m_builder.getInt32(static_cast<uint32_t>(mappedType)), typeField);
                 }
 
+                if (!safe)
+                    varPtr.type = Compiler::StaticType::Unknown;
+
                 createValueStore(arg.second, varPtr.stackPtr, type, varPtr.type);
                 varPtr.type = type;
                 m_scopeVariables.back()[&varPtr] = varPtr.type;
@@ -660,7 +665,11 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
 
             case LLVMInstruction::Type::ReadVariable: {
                 assert(step.args.size() == 0);
-                const LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
+                LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
+
+                if (!isVariableTypeSafe(insPtr, varPtr.type))
+                    varPtr.type = Compiler::StaticType::Unknown;
+
                 step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr;
                 step.functionReturnReg->setType(varPtr.type);
                 break;
@@ -2091,7 +2100,7 @@ llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::St
     }
 }
 
-Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg)
+Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg) const
 {
     Compiler::StaticType ret = reg->type();
 
@@ -2213,6 +2222,166 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr)
     m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
 }
 
+bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const
+{
+    std::unordered_set<LLVMInstruction *> processed;
+    return isVariableTypeSafe(ins, expectedType, processed);
+}
+
+bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const
+{
+    /*
+     * The main part of the loop type analyzer.
+     *
+     * This is a recursive function which is called when variable read
+     * instruction is created. It checks the last write to the
+     * variable in one of the loop scopes.
+     *
+     * If the last write operation writes a value with a different
+     * type, it will return false, otherwise true.
+     *
+     * If the last written value is from a variable, this function
+     * is called for it to check its type safety (that's why it is
+     * recursive).
+     *
+     * If the variable had a write operation before (in the same,
+     * parent or child loop scope), it is checked recursively.
+     */
+
+    if (!ins)
+        return false;
+
+    /*
+     * If we are processing something that has been already
+     * processed, give up to avoid infinite recursion.
+     *
+     * This can happen in edge cases like this:
+     * var = var
+     *
+     * or this:
+     * x = y
+     * y = x
+     *
+     * This code isn't considered valid, so don't bother
+     * optimizing.
+     */
+    if (processed.find(ins.get()) != processed.cend())
+        return false;
+
+    processed.insert(ins.get());
+
+    assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end());
+    const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable);
+    auto scope = ins->loopScope;
+
+    // If we aren't in a loop, we're safe
+    if (!scope)
+        return true;
+
+    // If the loop scope contains a suspend and this is a non-warp script, the type may change between suspend and resume
+    if (scope->containsYield && !m_warp)
+        return false;
+
+    std::shared_ptr<LLVMInstruction> write;
+
+    // Find this instruction
+    auto it = std::find(m_variableInstructions.begin(), m_variableInstructions.end(), ins);
+    assert(it != m_variableInstructions.end());
+
+    // Find previous write instruction in this, parent or child loop scope
+    size_t index = it - m_variableInstructions.begin();
+
+    if (index > 0) {
+        bool found = false;
+
+        do {
+            index--;
+            write = m_variableInstructions[index];
+            found = (write->loopScope && write->type == LLVMInstruction::Type::WriteVariable && write->workVariable == ins->workVariable);
+        } while (index > 0 && !found);
+
+        if (found) {
+            // Check if the write operation is in this or child scope
+            auto parentScope = write->loopScope;
+
+            while (parentScope && parentScope != scope)
+                parentScope = parentScope->parentScope;
+
+            if (!parentScope) {
+                // Check if the write operation is in any of the parent scopes
+                parentScope = scope;
+
+                do {
+                    parentScope = parentScope->parentScope;
+                } while (parentScope && parentScope != write->loopScope);
+            }
+
+            // If there was a write operation before this instruction (in this, parent or child scope), check it
+            if (parentScope) {
+                if (parentScope == scope)
+                    return isVariableWriteResultTypeSafe(write, expectedType, true, processed);
+                else
+                    return isVariableTypeSafe(write, expectedType, processed);
+            }
+        }
+    }
+
+    //  Get last write operation
+    write = nullptr;
+
+    // Find root loop scope
+    auto checkScope = scope;
+
+    while (checkScope->parentScope) {
+        checkScope = checkScope->parentScope;
+    }
+
+    // Find last loop scope (may be a parent or child scope)
+    while (checkScope) {
+        auto it = varPtr.loopVariableWrites.find(checkScope);
+
+        if (it != varPtr.loopVariableWrites.cend()) {
+            assert(!it->second.empty());
+            write = it->second.back();
+        }
+
+        if (checkScope->childScopes.empty())
+            checkScope = nullptr;
+        else
+            checkScope = checkScope->childScopes.back();
+    }
+
+    // If there aren't any write operations, we're safe
+    if (!write)
+        return true;
+
+    bool safe = true;
+
+    if (ins->type == LLVMInstruction::Type::WriteVariable)
+        safe = isVariableWriteResultTypeSafe(ins, expectedType, false, processed);
+
+    if (safe)
+        return isVariableWriteResultTypeSafe(write, expectedType, false, processed);
+    else
+        return false;
+}
+
+bool LLVMCodeBuilder::isVariableWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed)
+    const
+{
+    const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable);
+
+    // If the write operation writes the value of another variable, recursively check its type safety
+    // TODO: Check get list item instruction
+    auto argIns = ins->args[0].second->instruction;
+
+    if (argIns && argIns->type == LLVMInstruction::Type::ReadVariable)
+        return isVariableTypeSafe(argIns, expectedType, processed);
+
+    // Check written type
+    return optimizeRegisterType(ins->args[0].second) == expectedType && (varPtr.type == expectedType || ignoreSavedType);
+}
+
 LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
 {
     return createOp({ type, currentLoopScope() }, retType, argType, args);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 4a34ffd2..79977e29 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -137,7 +137,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType);
         llvm::Value *castRawValue(LLVMRegister *reg, Compiler::StaticType targetType);
         llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType);
-        Compiler::StaticType optimizeRegisterType(LLVMRegister *reg);
+        Compiler::StaticType optimizeRegisterType(LLVMRegister *reg) const;
         llvm::Type *getType(Compiler::StaticType type);
         Compiler::StaticType getProcedureArgType(BlockPrototype::ArgType type);
         llvm::Value *isNaN(llvm::Value *num);
@@ -149,6 +149,9 @@ class LLVMCodeBuilder : public ICodeBuilder
         void reloadVariables(llvm::Value *targetVariables);
         void reloadLists();
         void updateListDataPtr(const LLVMListPtr &listPtr);
+        bool isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const;
+        bool isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const;
+        bool isVariableWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed) const;
 
         LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
         LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 723c8101..c0a8ece8 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2832,6 +2832,17 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
     v = m_builder->addVariableValue(localVar.get());
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue(12.5);
+        m_builder->createVariableWrite(globalVar.get(), v);
+    }
+    m_builder->endLoop();
+
+    v = m_builder->addVariableValue(globalVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
     std::string expected =
         "hello world\n"
         "-4.8\n";
@@ -2851,6 +2862,20 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    globalVar->setValue("test");
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "");
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    globalVar->setValue(true);
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "true\n");
 }
 
 TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend)
@@ -3893,6 +3918,653 @@ TEST_F(LLVMCodeBuilderTest, LoopVariables)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis1)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a string is assigned later
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue("test");
+        m_builder->createVariableWrite(localVar.get(), v);
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis2)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is known here because a number is assigned later
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue(12.5);
+        m_builder->createVariableWrite(localVar.get(), v);
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "12.5\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis3)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(0);
+    m_builder->createVariableWrite(globalVar.get(), v);
+
+    v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a variable with unknown type is assigned (the variable has unknown type because a string is assigned later)
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue("test");
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis4)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(0);
+    m_builder->createVariableWrite(globalVar.get(), v);
+
+    v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is known here because a variable is assigned which has a number assigned later
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue(12.5);
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "12.5\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis5)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue("test");
+    m_builder->createVariableWrite(globalVar.get(), v);
+
+    v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(3);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a string variable is assigned later which has a number assigned as well
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addVariableValue(globalVar.get());
+        m_builder->createVariableWrite(localVar.get(), v);
+
+        v = m_builder->addConstValue(12.5);
+        m_builder->createVariableWrite(globalVar.get(), v);
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "12.5\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis6)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is known here because a variable with known type is assigned later (even though the variable has a string assigned later, there's a number assigned before the read operation)
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue(10);
+            m_builder->createVariableWrite(globalVar.get(), v);
+
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue("test");
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "10\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis7)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a variable with a known, but different type is assigned later
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue("test");
+            m_builder->createVariableWrite(globalVar.get(), v);
+
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue(10);
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis8)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a variable with a known, but different type is assigned later
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue("test");
+            m_builder->createVariableWrite(globalVar.get(), v);
+
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis9)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "", "123");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue(5);
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+
+        // Type is unknown here because a variable of unknown type is assigned before the read operation
+        v = m_builder->addVariableValue(localVar.get());
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue(10);
+            m_builder->createVariableWrite(globalVar.get(), v);
+
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue("test");
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5\n"
+        "5\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis10)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "", "123");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue("test");
+        m_builder->createVariableWrite(globalVar.get(), v);
+
+        v = m_builder->addConstValue(3);
+        m_builder->beginRepeatLoop(v);
+        {
+            // Type is unknown here because a variable of unknown type is assigned later
+            v = m_builder->addVariableValue(localVar.get());
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue(12.5);
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "123\n"
+        "0\n"
+        "12.5\n"
+        "12.5\n"
+        "0\n"
+        "12.5\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis11)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "", "123");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue("test");
+        m_builder->createVariableWrite(globalVar.get(), v);
+
+        v = m_builder->addConstValue(3);
+        m_builder->beginRepeatLoop(v);
+        {
+            // Type is unknown here because the variable is assigned to itself later
+            // This case is not checked because the code isn't considered valid
+            v = m_builder->addVariableValue(localVar.get());
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addVariableValue(localVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addConstValue(12.5);
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "123\n"
+        "123\n"
+        "123\n"
+        "123\n"
+        "123\n"
+        "123\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, VariableLoopTypeAnalysis12)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalVar = std::make_shared<Variable>("", "");
+    stage.addVariable(globalVar);
+
+    auto localVar = std::make_shared<Variable>("", "", "123");
+    sprite.addVariable(localVar);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue("test");
+        m_builder->createVariableWrite(globalVar.get(), v);
+
+        v = m_builder->addConstValue(3);
+        m_builder->beginRepeatLoop(v);
+        {
+            // Type is unknown here because another variable is assigned later, but it has this variable assigned
+            // This case is not checked because the code isn't considered valid
+            v = m_builder->addVariableValue(localVar.get());
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addVariableValue(globalVar.get());
+            m_builder->createVariableWrite(localVar.get(), v);
+
+            v = m_builder->addVariableValue(localVar.get());
+            m_builder->createVariableWrite(globalVar.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "123\n"
+        "0\n"
+        "0\n"
+        "0\n"
+        "0\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, LoopLists)
 {
     Stage stage;

From 30c7ad9295e229f5d3af0a7dc2b0b9c4a8e07a77 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 17 Jan 2025 12:35:37 +0100
Subject: [PATCH 247/259] LLVMCodeBuilder: Store loop condition info in
 instructions

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 66 +++++++++++--------
 .../engine/internal/llvm/llvmcodebuilder.h    |  1 +
 .../engine/internal/llvm/llvminstruction.h    |  6 +-
 3 files changed, 43 insertions(+), 30 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index d674bc44..d3a82b90 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1227,7 +1227,7 @@ CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName,
 {
     assert(argTypes.size() == args.size());
 
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::FunctionCall, currentLoopScope());
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::FunctionCall, currentLoopScope(), m_loopCondition);
     ins->functionName = functionName;
 
     for (size_t i = 0; i < args.size(); i++)
@@ -1278,7 +1278,7 @@ CompilerValue *LLVMCodeBuilder::addLocalVariableValue(CompilerLocalVariable *var
 
 CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 {
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::ReadVariable, currentLoopScope());
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::ReadVariable, currentLoopScope(), m_loopCondition);
     ins->workVariable = variable;
 
     if (m_variablePtrs.find(variable) == m_variablePtrs.cend())
@@ -1295,7 +1295,7 @@ CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable)
 
 CompilerValue *LLVMCodeBuilder::addListContents(List *list)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope(), m_loopCondition);
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1306,7 +1306,7 @@ CompilerValue *LLVMCodeBuilder::addListContents(List *list)
 
 CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
 {
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::GetListItem, currentLoopScope());
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::GetListItem, currentLoopScope(), m_loopCondition);
     ins->workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1324,7 +1324,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
 
 CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope(), m_loopCondition);
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1335,7 +1335,7 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item
 
 CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope(), m_loopCondition);
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1346,7 +1346,7 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
 
 CompilerValue *LLVMCodeBuilder::addListSize(List *list)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope(), m_loopCondition);
     ins.workList = list;
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
@@ -1370,7 +1370,7 @@ CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name)
 
     const auto index = it - argNames.begin();
     const Compiler::StaticType type = getProcedureArgType(m_procedurePrototype->argumentTypes()[index]);
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::ProcedureArg, currentLoopScope());
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::ProcedureArg, currentLoopScope(), m_loopCondition);
     auto ret = std::make_shared<LLVMRegister>(type);
     ret->isRawValue = (type != Compiler::StaticType::Unknown);
     ins->functionReturnReg = ret.get();
@@ -1550,7 +1550,7 @@ void LLVMCodeBuilder::createLocalVariableWrite(CompilerLocalVariable *variable,
 
 void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope(), m_loopCondition);
     ins.workVariable = variable;
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value });
 
@@ -1567,7 +1567,7 @@ void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *val
 
 void LLVMCodeBuilder::createListClear(List *list)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope(), m_loopCondition);
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void);
 
@@ -1577,7 +1577,7 @@ void LLVMCodeBuilder::createListClear(List *list)
 
 void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope(), m_loopCondition);
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index });
 
@@ -1587,7 +1587,7 @@ void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index)
 
 void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope(), m_loopCondition);
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item });
 
@@ -1597,7 +1597,7 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
 
 void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope(), m_loopCondition);
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
 
@@ -1607,7 +1607,7 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile
 
 void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item)
 {
-    LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope(), m_loopCondition);
     ins.workList = list;
     createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item });
 
@@ -1617,24 +1617,26 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil
 
 void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond)
 {
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginIf, currentLoopScope());
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginIf, currentLoopScope(), m_loopCondition);
     ins->args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
 }
 
 void LLVMCodeBuilder::beginElseBranch()
 {
-    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginElse, currentLoopScope()));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginElse, currentLoopScope(), m_loopCondition));
 }
 
 void LLVMCodeBuilder::endIf()
 {
-    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::EndIf, currentLoopScope()));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::EndIf, currentLoopScope(), m_loopCondition));
 }
 
 void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count)
 {
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope());
+    assert(!m_loopCondition);
+
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope(), m_loopCondition);
     ins->args.push_back({ Compiler::StaticType::Number, static_cast<LLVMRegister *>(count) });
     m_instructions.push_back(ins);
     pushLoopScope(false);
@@ -1642,7 +1644,10 @@ void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count)
 
 void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond)
 {
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope());
+    assert(m_loopCondition);
+    m_loopCondition = false;
+
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope(), m_loopCondition);
     ins->args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
     pushLoopScope(false);
@@ -1650,7 +1655,10 @@ void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond)
 
 void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond)
 {
-    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope());
+    assert(m_loopCondition);
+    m_loopCondition = false;
+
+    auto ins = std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope(), m_loopCondition);
     ins->args.push_back({ Compiler::StaticType::Bool, static_cast<LLVMRegister *>(cond) });
     m_instructions.push_back(ins);
     pushLoopScope(false);
@@ -1658,21 +1666,23 @@ void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond)
 
 void LLVMCodeBuilder::beginLoopCondition()
 {
-    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope()));
+    assert(!m_loopCondition);
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope(), m_loopCondition));
+    m_loopCondition = true;
 }
 
 void LLVMCodeBuilder::endLoop()
 {
     if (!m_warp)
-        m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Yield, currentLoopScope()));
+        m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition));
 
-    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::EndLoop, currentLoopScope()));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::EndLoop, currentLoopScope(), m_loopCondition));
     popLoopScope();
 }
 
 void LLVMCodeBuilder::yield()
 {
-    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Yield, currentLoopScope()));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition));
 
     if (m_loopScope >= 0)
         m_loopScopes[m_loopScope]->containsYield = true;
@@ -1680,7 +1690,7 @@ void LLVMCodeBuilder::yield()
 
 void LLVMCodeBuilder::createStop()
 {
-    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Stop, currentLoopScope()));
+    m_instructions.push_back(std::make_shared<LLVMInstruction>(LLVMInstruction::Type::Stop, currentLoopScope(), m_loopCondition));
 }
 
 void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args)
@@ -1693,7 +1703,7 @@ void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compi
     for (BlockPrototype::ArgType type : procedureArgs)
         types.push_back(getProcedureArgType(type));
 
-    LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope());
+    LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope(), m_loopCondition);
     ins.procedurePrototype = prototype;
     createOp(ins, Compiler::StaticType::Void, types, args);
 }
@@ -2384,12 +2394,12 @@ bool LLVMCodeBuilder::isVariableWriteResultTypeSafe(std::shared_ptr<LLVMInstruct
 
 LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
 {
-    return createOp({ type, currentLoopScope() }, retType, argType, args);
+    return createOp({ type, currentLoopScope(), m_loopCondition }, retType, argType, args);
 }
 
 LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args)
 {
-    return createOp({ type, currentLoopScope() }, retType, argTypes, args);
+    return createOp({ type, currentLoopScope(), m_loopCondition }, retType, argTypes, args);
 }
 
 LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 79977e29..7a657d20 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -236,6 +236,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         std::vector<std::shared_ptr<LLVMLoopScope>> m_loopScopes;
         long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase
         std::vector<long> m_loopScopeTree;
+        bool m_loopCondition = false; // whether we're currently compiling a loop condition
         std::vector<std::shared_ptr<LLVMInstruction>> m_variableInstructions;
         std::vector<std::vector<llvm::Value *>> m_heap; // scopes
 
diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h
index 4f1787f5..8e88e887 100644
--- a/src/dev/engine/internal/llvm/llvminstruction.h
+++ b/src/dev/engine/internal/llvm/llvminstruction.h
@@ -78,9 +78,10 @@ struct LLVMInstruction
             ProcedureArg
         };
 
-        LLVMInstruction(Type type, std::shared_ptr<LLVMLoopScope> loopScope) :
+        LLVMInstruction(Type type, std::shared_ptr<LLVMLoopScope> loopScope, bool loopCondition) :
             type(type),
-            loopScope(loopScope)
+            loopScope(loopScope),
+            loopCondition(loopCondition)
         {
         }
 
@@ -95,6 +96,7 @@ struct LLVMInstruction
         BlockPrototype *procedurePrototype = nullptr;
         size_t procedureArgIndex = 0;
         std::shared_ptr<LLVMLoopScope> loopScope;
+        bool loopCondition = false; // whether the instruction is part of a loop condition
 };
 
 } // namespace libscratchcpp

From 3bc6370c125b60c146afd1fde5c5450828cf1bdf Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 17 Jan 2025 12:36:24 +0100
Subject: [PATCH 248/259] LLVMCodeBuilder: Force variable heap pointer when in
 non-warp loop cond

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  |  2 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 57 +++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index d3a82b90..a0166674 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -670,7 +670,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 if (!isVariableTypeSafe(insPtr, varPtr.type))
                     varPtr.type = Compiler::StaticType::Unknown;
 
-                step.functionReturnReg->value = varPtr.onStack ? varPtr.stackPtr : varPtr.heapPtr;
+                step.functionReturnReg->value = varPtr.onStack && !(step.loopCondition && !m_warp) ? varPtr.stackPtr : varPtr.heapPtr;
                 step.functionReturnReg->setType(varPtr.type);
                 break;
             }
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index c0a8ece8..2789deec 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -2843,6 +2843,28 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
     v = m_builder->addVariableValue(globalVar.get());
     m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
 
+    v = m_builder->addConstValue(0);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    m_builder->beginLoopCondition();
+    v = m_builder->createCmpLT(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(3));
+    m_builder->beginWhileLoop(v);
+    m_builder->endLoop();
+
+    v = m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
+    v = m_builder->addConstValue(0);
+    m_builder->createVariableWrite(localVar.get(), v);
+
+    m_builder->beginLoopCondition();
+    v = m_builder->createCmpEQ(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(2));
+    m_builder->beginRepeatUntilLoop(v);
+    m_builder->endLoop();
+
+    v = m_builder->addVariableValue(localVar.get());
+    m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v });
+
     std::string expected =
         "hello world\n"
         "-4.8\n";
@@ -2876,6 +2898,41 @@ TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend)
     testing::internal::CaptureStdout();
     code->run(ctx.get());
     ASSERT_EQ(testing::internal::GetCapturedStdout(), "true\n");
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    localVar->setValue(1);
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "");
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    localVar->setValue("2");
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "");
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    localVar->setValue(3);
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "3\n");
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    localVar->setValue(1);
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "");
+    ASSERT_FALSE(code->isFinished(ctx.get()));
+
+    localVar->setValue(2);
+
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), "2\n");
 }
 
 TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend)

From ad04bcef40821f80dcaf90633a1c5eb39b1b52a5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 18 Jan 2025 11:24:47 +0100
Subject: [PATCH 249/259] LLVMCodeBuilder: Store list read/write instructions

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 15 +++++++++++++--
 src/dev/engine/internal/llvm/llvmcodebuilder.h   |  1 +
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index a0166674..a8e9ec6f 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1319,6 +1319,7 @@ CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index)
     ins->functionReturnReg = ret.get();
 
     m_instructions.push_back(ins);
+    m_listInstructions.push_back(m_instructions.back());
     return addReg(ret, ins);
 }
 
@@ -1330,7 +1331,9 @@ CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
 
-    return createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item });
+    auto ret = createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item });
+    m_listInstructions.push_back(m_instructions.back());
+    return ret;
 }
 
 CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
@@ -1341,7 +1344,9 @@ CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item)
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
 
-    return createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item });
+    auto ret = createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item });
+    m_listInstructions.push_back(m_instructions.back());
+    return ret;
 }
 
 CompilerValue *LLVMCodeBuilder::addListSize(List *list)
@@ -1593,6 +1598,8 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
+
+    m_listInstructions.push_back(m_instructions.back());
 }
 
 void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item)
@@ -1603,6 +1610,8 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
+
+    m_listInstructions.push_back(m_instructions.back());
 }
 
 void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item)
@@ -1613,6 +1622,8 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil
 
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
+
+    m_listInstructions.push_back(m_instructions.back());
 }
 
 void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond)
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 7a657d20..d8d7b6f9 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -238,6 +238,7 @@ class LLVMCodeBuilder : public ICodeBuilder
         std::vector<long> m_loopScopeTree;
         bool m_loopCondition = false; // whether we're currently compiling a loop condition
         std::vector<std::shared_ptr<LLVMInstruction>> m_variableInstructions;
+        std::vector<std::shared_ptr<LLVMInstruction>> m_listInstructions;
         std::vector<std::vector<llvm::Value *>> m_heap; // scopes
 
         std::shared_ptr<ExecutableCode> m_output;

From 2ba3e126d1ddef3f28902898c2e013e8d831bb7b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 18 Jan 2025 11:33:23 +0100
Subject: [PATCH 250/259] LLVMCodeBuilder: Store loop list write instructions

---
 src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 15 +++++++++++++++
 src/dev/engine/internal/llvm/llvmlistptr.h       |  7 +++++++
 2 files changed, 22 insertions(+)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index a8e9ec6f..a0bc7d1e 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -1599,6 +1599,11 @@ void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item)
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
 
+    if (m_loopScope >= 0) {
+        auto scope = m_loopScopes[m_loopScope];
+        m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back());
+    }
+
     m_listInstructions.push_back(m_instructions.back());
 }
 
@@ -1611,6 +1616,11 @@ void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, Compile
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
 
+    if (m_loopScope >= 0) {
+        auto scope = m_loopScopes[m_loopScope];
+        m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back());
+    }
+
     m_listInstructions.push_back(m_instructions.back());
 }
 
@@ -1623,6 +1633,11 @@ void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, Compil
     if (m_listPtrs.find(list) == m_listPtrs.cend())
         m_listPtrs[list] = LLVMListPtr();
 
+    if (m_loopScope >= 0) {
+        auto scope = m_loopScopes[m_loopScope];
+        m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back());
+    }
+
     m_listInstructions.push_back(m_instructions.back());
 }
 
diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h
index 6b427572..ca97bc0e 100644
--- a/src/dev/engine/internal/llvm/llvmlistptr.h
+++ b/src/dev/engine/internal/llvm/llvmlistptr.h
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <scratchcpp/dev/compiler.h>
+#include <unordered_map>
 
 namespace llvm
 {
@@ -14,6 +15,9 @@ class Value;
 namespace libscratchcpp
 {
 
+class LLVMLoopScope;
+class LLVMInstruction;
+
 struct LLVMListPtr
 {
         llvm::Value *ptr = nullptr;
@@ -22,6 +26,9 @@ struct LLVMListPtr
         llvm::Value *allocatedSizePtr = nullptr;
         llvm::Value *dataPtrDirty = nullptr;
         Compiler::StaticType type = Compiler::StaticType::Unknown;
+
+        // Used in build phase to check the type safety of lists in loops
+        std::unordered_map<std::shared_ptr<LLVMLoopScope>, std::vector<std::shared_ptr<LLVMInstruction>>> loopListWrites; // loop scope, write instructions
 };
 
 } // namespace libscratchcpp

From 1ee060a64bfcb0fe327a221b18bf72c8789bad17 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 18 Jan 2025 12:48:08 +0100
Subject: [PATCH 251/259] LLVMCodeBuilder: Implement loop type analysis for
 lists

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 116 ++-
 .../engine/internal/llvm/llvmcodebuilder.h    |   6 +-
 test/dev/llvm/llvmcodebuilder_test.cpp        | 825 ++++++++++++++++++
 3 files changed, 907 insertions(+), 40 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index a0bc7d1e..c19e4bcb 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -23,6 +23,9 @@ using namespace libscratchcpp;
 static std::unordered_map<ValueType, Compiler::StaticType>
     TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } };
 
+static const std::unordered_set<LLVMInstruction::Type>
+    VAR_LIST_READ_INSTRUCTIONS = { LLVMInstruction::Type::ReadVariable, LLVMInstruction::Type::GetListItem, LLVMInstruction::Type::GetListItemIndex, LLVMInstruction::Type::ListContainsItem };
+
 LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *procedurePrototype) :
     m_ctx(ctx),
     m_target(ctx->target()),
@@ -632,7 +635,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
                 varPtr.changed = true;
 
-                const bool safe = isVariableTypeSafe(insPtr, varPtr.type);
+                const bool safe = isVarOrListTypeSafe(insPtr, varPtr.type);
 
                 // Initialize stack variable on first assignment
                 if (!varPtr.onStack) {
@@ -667,7 +670,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 assert(step.args.size() == 0);
                 LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable];
 
-                if (!isVariableTypeSafe(insPtr, varPtr.type))
+                if (!isVarOrListTypeSafe(insPtr, varPtr.type))
                     varPtr.type = Compiler::StaticType::Unknown;
 
                 step.functionReturnReg->value = varPtr.onStack && !(step.loopCondition && !m_warp) ? varPtr.stackPtr : varPtr.heapPtr;
@@ -693,7 +696,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::RemoveListItem: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
+
+                if (!isVarOrListTypeSafe(insPtr, listPtr.type))
+                    listPtr.type = Compiler::StaticType::Unknown;
 
                 // Range check
                 llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
@@ -722,6 +728,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(arg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
+                const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type);
                 auto &typeMap = m_scopeLists.back();
 
                 if (typeMap.find(&listPtr) == typeMap.cend()) {
@@ -732,6 +739,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     typeMap[&listPtr] = listPtr.type;
                 }
 
+                if (!safe)
+                    listPtr.type = Compiler::StaticType::Unknown;
+
                 // Check if enough space is allocated
                 llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
@@ -767,6 +777,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
+                const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type);
                 auto &typeMap = m_scopeLists.back();
 
                 if (typeMap.find(&listPtr) == typeMap.cend()) {
@@ -777,6 +788,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     typeMap[&listPtr] = listPtr.type;
                 }
 
+                if (!safe)
+                    listPtr.type = Compiler::StaticType::Unknown;
+
                 llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
 
                 // Range check
@@ -813,6 +827,9 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
+                if (!isVarOrListTypeSafe(insPtr, listPtr.type))
+                    listPtr.type = Compiler::StaticType::Unknown;
+
                 // Range check
                 llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
@@ -856,7 +873,10 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::GetListItem: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
+
+                if (!isVarOrListTypeSafe(insPtr, listPtr.type))
+                    listPtr.type = Compiler::StaticType::Unknown;
 
                 llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0));
                 llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr);
@@ -884,7 +904,11 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::GetListItemIndex: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
+
+                if (!isVarOrListTypeSafe(insPtr, listPtr.type))
+                    listPtr.type = Compiler::StaticType::Unknown;
+
                 step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second), m_builder.getDoubleTy());
                 break;
             }
@@ -892,7 +916,11 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
             case LLVMInstruction::Type::ListContainsItem: {
                 assert(step.args.size() == 1);
                 const auto &arg = step.args[0];
-                const LLVMListPtr &listPtr = m_listPtrs[step.workList];
+                LLVMListPtr &listPtr = m_listPtrs[step.workList];
+
+                if (!isVarOrListTypeSafe(insPtr, listPtr.type))
+                    listPtr.type = Compiler::StaticType::Unknown;
+
                 llvm::Value *index = getListItemIndex(listPtr, arg.second);
                 step.functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true));
                 break;
@@ -2258,30 +2286,31 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr)
     m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty);
 }
 
-bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const
+bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const
 {
     std::unordered_set<LLVMInstruction *> processed;
-    return isVariableTypeSafe(ins, expectedType, processed);
+    return isVarOrListTypeSafe(ins, expectedType, processed);
 }
 
-bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const
+bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const
 {
     /*
      * The main part of the loop type analyzer.
      *
-     * This is a recursive function which is called when variable read
-     * instruction is created. It checks the last write to the
-     * variable in one of the loop scopes.
+     * This is a recursive function which is called when variable
+     * or list instruction is created. It checks the last write to
+     * the variable or list in one of the loop scopes.
      *
      * If the last write operation writes a value with a different
      * type, it will return false, otherwise true.
      *
-     * If the last written value is from a variable, this function
-     * is called for it to check its type safety (that's why it is
-     * recursive).
+     * If the last written value is from a variable or list, this
+     * function is called for it to check its type safety (that's
+     * why it is recursive).
      *
-     * If the variable had a write operation before (in the same,
-     * parent or child loop scope), it is checked recursively.
+     * If the variable or list had a write operation before (in
+     * the same, parent or child loop scope), it is checked
+     * recursively.
      */
 
     if (!ins)
@@ -2307,7 +2336,9 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, C
     processed.insert(ins.get());
 
     assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end());
-    const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable);
+    const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr;
+    const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr;
+    assert((varPtr || listPtr) && !(varPtr && listPtr));
     auto scope = ins->loopScope;
 
     // If we aren't in a loop, we're safe
@@ -2319,21 +2350,23 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, C
         return false;
 
     std::shared_ptr<LLVMInstruction> write;
+    const auto &instructions = varPtr ? m_variableInstructions : m_listInstructions;
 
     // Find this instruction
-    auto it = std::find(m_variableInstructions.begin(), m_variableInstructions.end(), ins);
-    assert(it != m_variableInstructions.end());
+    auto it = std::find(instructions.begin(), instructions.end(), ins);
+    assert(it != instructions.end());
 
     // Find previous write instruction in this, parent or child loop scope
-    size_t index = it - m_variableInstructions.begin();
+    size_t index = it - instructions.begin();
 
     if (index > 0) {
         bool found = false;
 
         do {
             index--;
-            write = m_variableInstructions[index];
-            found = (write->loopScope && write->type == LLVMInstruction::Type::WriteVariable && write->workVariable == ins->workVariable);
+            write = instructions[index];
+            const bool isWrite = (VAR_LIST_READ_INSTRUCTIONS.find(write->type) == VAR_LIST_READ_INSTRUCTIONS.cend());
+            found = (write->loopScope && isWrite && write->workVariable == ins->workVariable);
         } while (index > 0 && !found);
 
         if (found) {
@@ -2355,13 +2388,15 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, C
             // If there was a write operation before this instruction (in this, parent or child scope), check it
             if (parentScope) {
                 if (parentScope == scope)
-                    return isVariableWriteResultTypeSafe(write, expectedType, true, processed);
+                    return isVarOrListWriteResultTypeSafe(write, expectedType, true, processed);
                 else
-                    return isVariableTypeSafe(write, expectedType, processed);
+                    return isVarOrListTypeSafe(write, expectedType, processed);
             }
         }
     }
 
+    const auto &loopWrites = varPtr ? varPtr->loopVariableWrites : listPtr->loopListWrites;
+
     //  Get last write operation
     write = nullptr;
 
@@ -2374,9 +2409,9 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, C
 
     // Find last loop scope (may be a parent or child scope)
     while (checkScope) {
-        auto it = varPtr.loopVariableWrites.find(checkScope);
+        auto it = loopWrites.find(checkScope);
 
-        if (it != varPtr.loopVariableWrites.cend()) {
+        if (it != loopWrites.cend()) {
             assert(!it->second.empty());
             write = it->second.back();
         }
@@ -2393,29 +2428,36 @@ bool LLVMCodeBuilder::isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, C
 
     bool safe = true;
 
-    if (ins->type == LLVMInstruction::Type::WriteVariable)
-        safe = isVariableWriteResultTypeSafe(ins, expectedType, false, processed);
+    if (VAR_LIST_READ_INSTRUCTIONS.find(ins->type) == VAR_LIST_READ_INSTRUCTIONS.cend()) // write
+        safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, processed);
 
     if (safe)
-        return isVariableWriteResultTypeSafe(write, expectedType, false, processed);
+        return isVarOrListWriteResultTypeSafe(write, expectedType, false, processed);
     else
         return false;
 }
 
-bool LLVMCodeBuilder::isVariableWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed)
+bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed)
     const
 {
-    const LLVMVariablePtr &varPtr = m_variablePtrs.at(ins->workVariable);
+    const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr;
+    const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr;
+    assert((varPtr || listPtr) && !(varPtr && listPtr));
 
     // If the write operation writes the value of another variable, recursively check its type safety
-    // TODO: Check get list item instruction
-    auto argIns = ins->args[0].second->instruction;
+    const auto arg = ins->args.back().second; // value is always the last argument
+    auto argIns = arg->instruction;
 
-    if (argIns && argIns->type == LLVMInstruction::Type::ReadVariable)
-        return isVariableTypeSafe(argIns, expectedType, processed);
+    if (argIns && (argIns->type == LLVMInstruction::Type::ReadVariable || argIns->type == LLVMInstruction::Type::GetListItem))
+        return isVarOrListTypeSafe(argIns, expectedType, processed);
 
     // Check written type
-    return optimizeRegisterType(ins->args[0].second) == expectedType && (varPtr.type == expectedType || ignoreSavedType);
+    const bool typeMatches = (optimizeRegisterType(arg) == expectedType);
+
+    if (varPtr)
+        return typeMatches && (varPtr->type == expectedType || ignoreSavedType);
+    else
+        return typeMatches && (listPtr->type == expectedType || ignoreSavedType);
 }
 
 LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args)
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index d8d7b6f9..22fa1609 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -149,9 +149,9 @@ class LLVMCodeBuilder : public ICodeBuilder
         void reloadVariables(llvm::Value *targetVariables);
         void reloadLists();
         void updateListDataPtr(const LLVMListPtr &listPtr);
-        bool isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const;
-        bool isVariableTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const;
-        bool isVariableWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed) const;
+        bool isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const;
+        bool isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const;
+        bool isVarOrListWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed) const;
 
         LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
         LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 2789deec..e2ebe5d1 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -4815,6 +4815,831 @@ TEST_F(LLVMCodeBuilderTest, LoopLists)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis1)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a string is added later
+        v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1));
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue("test");
+        m_builder->createListAppend(localList.get(), v);
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "0\n"
+        "1\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis2)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is known here because a number is assigned later
+        v = m_builder->addConstValue(0);
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue(12.5);
+        m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "12.5\n"
+        "-1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis3)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(globalList.get());
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(0);
+    m_builder->createListAppend(globalList.get(), v);
+
+    v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a list item with unknown type is inserted (the item has unknown type because a string is assigned later)
+        v = m_builder->addConstValue(0);
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListInsert(localList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addConstValue("test");
+            m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "2\n"
+        "1\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis4)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(globalList.get());
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(0);
+    m_builder->createListAppend(globalList.get(), v);
+
+    v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is known here because a number list item is added later
+        v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1));
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListAppend(localList.get(), v);
+
+            v = m_builder->addConstValue(12.5);
+            m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "12.5\n"
+        "0\n"
+        "1\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis5)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(globalList.get());
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue("test");
+    m_builder->createListAppend(globalList.get(), v);
+
+    v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(3);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a string list item is assigned later which becomes a number later
+        v = m_builder->addConstValue(0);
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+        m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+
+        v = m_builder->addConstValue(12.5);
+        m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "-1\n"
+        "0\n"
+        "12.5\n"
+        "-1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a list item with unknown type is added later (even though the list item has the original type assigned, it cannot be determined at compile time)
+        v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1));
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue(10);
+            m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListAppend(localList.get(), v);
+
+            v = m_builder->addConstValue("test");
+            m_builder->createListAppend(globalList.get(), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "10\n"
+        "0\n"
+        "1\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis7)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a list item with unknown type is assigned later
+        v = m_builder->addConstValue(0);
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue("test");
+            m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addConstValue(10);
+            m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "-1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis8)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a list item with a unknown type is assigned later
+        v = m_builder->addConstValue(0);
+        v = m_builder->addListItem(localList.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue("test");
+            m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "-1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis9)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+
+    createBuilder(&sprite, true);
+
+    m_builder->createListClear(localList.get());
+
+    CompilerValue *v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(localList.get(), v);
+
+    v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addConstValue(5);
+            m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+
+        // Type is unknown here because a list item with unknown type is assigned before the read operation
+        v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addConstValue(2);
+        m_builder->beginRepeatLoop(v);
+        {
+            v = m_builder->addConstValue(10);
+            m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addConstValue("test");
+            m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5\n"
+        "0\n"
+        "1\n"
+        "5\n"
+        "0\n"
+        "1\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis10)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+    localList->append(123);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue("test");
+        m_builder->createListAppend(globalList.get(), v);
+
+        v = m_builder->addConstValue(3);
+        m_builder->beginRepeatLoop(v);
+        {
+            // Type is unknown here because a list item with unknown type is assigned later
+            v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0));
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123));
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123));
+            m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addConstValue(12.5);
+            m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "123\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "-1\n"
+        "0\n"
+        "12.5\n"
+        "-1\n"
+        "0\n"
+        "12.5\n"
+        "-1\n"
+        "0\n"
+        "12.5\n"
+        "-1\n"
+        "0\n"
+        "12.5\n"
+        "-1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis11)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+    localList->append(123);
+    localList->append("10");
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue("test");
+        m_builder->createListAppend(globalList.get(), v);
+
+        v = m_builder->addConstValue(3);
+        m_builder->beginRepeatLoop(v);
+        {
+            // Type is unknown here because an item from the same list is assigned later, but the list has unknown type
+            v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0));
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123));
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123));
+            m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+            v = m_builder->addListItem(localList.get(), m_builder->addConstValue(1));
+            m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addConstValue(12.5);
+            m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "123\n"
+        "0\n"
+        "1\n"
+        "10\n"
+        "-1\n"
+        "0\n"
+        "10\n"
+        "-1\n"
+        "0\n"
+        "10\n"
+        "-1\n"
+        "0\n"
+        "10\n"
+        "-1\n"
+        "0\n"
+        "10\n"
+        "-1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
+TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis12)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto globalList = std::make_shared<List>("", "");
+    stage.addList(globalList);
+
+    auto localList = std::make_shared<List>("", "");
+    sprite.addList(localList);
+    localList->append(123);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(2);
+    m_builder->beginRepeatLoop(v);
+    {
+        v = m_builder->addConstValue("test");
+        m_builder->createListAppend(globalList.get(), v);
+
+        v = m_builder->addConstValue(3);
+        m_builder->beginRepeatLoop(v);
+        {
+            // Type is unknown here because an item from another list is assigned later, but it has an item from this list assigned
+            // This case is not checked because the code isn't considered valid
+            v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0));
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123));
+            m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+            v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123));
+            m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v);
+
+            v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0));
+            m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
+        }
+        m_builder->endLoop();
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "123\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "-1\n"
+        "0\n"
+        "0\n"
+        "-1\n"
+        "0\n"
+        "0\n"
+        "-1\n"
+        "0\n"
+        "0\n"
+        "-1\n"
+        "0\n"
+        "0\n"
+        "-1\n"
+        "0\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, StopNoWarp)
 {
     Sprite sprite;

From 17b37917793a92b07b35c56e3059e91ec58e3dea Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 18 Jan 2025 19:55:45 +0100
Subject: [PATCH 252/259] LLVMCodeBuilder: Add test for variable and list loop
 type analysis

---
 test/dev/llvm/llvmcodebuilder_test.cpp | 66 ++++++++++++++++++++++++++
 1 file changed, 66 insertions(+)

diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index e2ebe5d1..1da6bf08 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -5640,6 +5640,72 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis12)
     ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
 }
 
+TEST_F(LLVMCodeBuilderTest, VarAndListLoopTypeAnalysis)
+{
+    Stage stage;
+    Sprite sprite;
+    sprite.setEngine(&m_engine);
+    EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage));
+
+    auto var = std::make_shared<Variable>("", "");
+    sprite.addVariable(var);
+
+    auto list = std::make_shared<List>("", "");
+    sprite.addList(list);
+
+    createBuilder(&sprite, true);
+
+    CompilerValue *v = m_builder->addConstValue(-2);
+    m_builder->createVariableWrite(var.get(), v);
+
+    m_builder->createListClear(list.get());
+
+    v = m_builder->addConstValue(5.25);
+    m_builder->createListAppend(list.get(), v);
+
+    v = m_builder->addConstValue(3);
+    m_builder->beginRepeatLoop(v);
+    {
+        // Type is unknown here because a variable value with unknown type is added later
+        v = m_builder->createSub(m_builder->addListSize(list.get()), m_builder->addConstValue(1));
+        v = m_builder->addListItem(list.get(), v);
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListItemIndex(list.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
+
+        v = m_builder->addListContains(list.get(), m_builder->addConstValue(5.25));
+        m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v });
+
+        v = m_builder->addVariableValue(var.get());
+        m_builder->createListAppend(list.get(), v);
+
+        v = m_builder->addConstValue("test");
+        m_builder->createVariableWrite(var.get(), v);
+    }
+    m_builder->endLoop();
+
+    std::string expected =
+        "5.25\n"
+        "0\n"
+        "1\n"
+        "-2\n"
+        "0\n"
+        "1\n"
+        "0\n"
+        "0\n"
+        "1\n";
+
+    auto code = m_builder->finalize();
+    Script script(&sprite, nullptr, nullptr);
+    script.setCode(code);
+    Thread thread(&sprite, nullptr, &script);
+    auto ctx = code->createExecutionContext(&thread);
+    testing::internal::CaptureStdout();
+    code->run(ctx.get());
+    ASSERT_EQ(testing::internal::GetCapturedStdout(), expected);
+}
+
 TEST_F(LLVMCodeBuilderTest, StopNoWarp)
 {
     Sprite sprite;

From bd019d96ef6efff57e8edbec2a113d845b9e2892 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 18 Jan 2025 23:22:07 +0100
Subject: [PATCH 253/259] LLVMCodeBuilder: Optimize variables and lists
 recursively used in loops

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 59 ++++++++++---------
 .../engine/internal/llvm/llvmcodebuilder.h    |  4 +-
 2 files changed, 32 insertions(+), 31 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index c19e4bcb..04d23b39 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -728,7 +728,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(arg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
-                const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type);
                 auto &typeMap = m_scopeLists.back();
 
                 if (typeMap.find(&listPtr) == typeMap.cend()) {
@@ -739,7 +738,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     typeMap[&listPtr] = listPtr.type;
                 }
 
-                if (!safe)
+                if (!isVarOrListTypeSafe(insPtr, listPtr.type))
                     listPtr.type = Compiler::StaticType::Unknown;
 
                 // Check if enough space is allocated
@@ -777,7 +776,6 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                 Compiler::StaticType type = optimizeRegisterType(valueArg.second);
                 LLVMListPtr &listPtr = m_listPtrs[step.workList];
 
-                const bool safe = isVarOrListTypeSafe(insPtr, listPtr.type);
                 auto &typeMap = m_scopeLists.back();
 
                 if (typeMap.find(&listPtr) == typeMap.cend()) {
@@ -788,7 +786,7 @@ std::shared_ptr<ExecutableCode> LLVMCodeBuilder::finalize()
                     typeMap[&listPtr] = listPtr.type;
                 }
 
-                if (!safe)
+                if (!isVarOrListTypeSafe(insPtr, listPtr.type))
                     listPtr.type = Compiler::StaticType::Unknown;
 
                 llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr);
@@ -2289,10 +2287,11 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr)
 bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const
 {
     std::unordered_set<LLVMInstruction *> processed;
-    return isVarOrListTypeSafe(ins, expectedType, processed);
+    int counter = 0;
+    return isVarOrListTypeSafe(ins, expectedType, processed, counter);
 }
 
-bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const
+bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &log, int &c) const
 {
     /*
      * The main part of the loop type analyzer.
@@ -2318,22 +2317,20 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
 
     /*
      * If we are processing something that has been already
-     * processed, give up to avoid infinite recursion.
-     *
-     * This can happen in edge cases like this:
-     * var = var
+     * processed, it means there's a case like this:
+     * x = x
      *
      * or this:
      * x = y
+     * ...
      * y = x
      *
-     * This code isn't considered valid, so don't bother
-     * optimizing.
+     * Increment counter to ignore last n write operations.
      */
-    if (processed.find(ins.get()) != processed.cend())
-        return false;
-
-    processed.insert(ins.get());
+    if (log.find(ins.get()) != log.cend())
+        c++;
+    else
+        log.insert(ins.get());
 
     assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end());
     const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr;
@@ -2388,18 +2385,15 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
             // If there was a write operation before this instruction (in this, parent or child scope), check it
             if (parentScope) {
                 if (parentScope == scope)
-                    return isVarOrListWriteResultTypeSafe(write, expectedType, true, processed);
+                    return isVarOrListWriteResultTypeSafe(write, expectedType, true, log, c);
                 else
-                    return isVarOrListTypeSafe(write, expectedType, processed);
+                    return isVarOrListTypeSafe(write, expectedType, log, c);
             }
         }
     }
 
     const auto &loopWrites = varPtr ? varPtr->loopVariableWrites : listPtr->loopListWrites;
 
-    //  Get last write operation
-    write = nullptr;
-
     // Find root loop scope
     auto checkScope = scope;
 
@@ -2407,13 +2401,18 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
         checkScope = checkScope->parentScope;
     }
 
-    // Find last loop scope (may be a parent or child scope)
+    // Find n-th last write operation based on counter (may be in a parent or child scope)
+    std::vector<std::shared_ptr<LLVMInstruction>> lastWrites;
+
     while (checkScope) {
         auto it = loopWrites.find(checkScope);
 
         if (it != loopWrites.cend()) {
             assert(!it->second.empty());
-            write = it->second.back();
+            const auto &writes = it->second;
+
+            for (auto w : writes)
+                lastWrites.push_back(w);
         }
 
         if (checkScope->childScopes.empty())
@@ -2422,22 +2421,24 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
             checkScope = checkScope->childScopes.back();
     }
 
-    // If there aren't any write operations, we're safe
-    if (!write)
+    // If there aren't any write operations or all of them are ignored, we're safe
+    if (c >= lastWrites.size())
         return true;
 
+    write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes
+
     bool safe = true;
 
     if (VAR_LIST_READ_INSTRUCTIONS.find(ins->type) == VAR_LIST_READ_INSTRUCTIONS.cend()) // write
-        safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, processed);
+        safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, log, c);
 
     if (safe)
-        return isVarOrListWriteResultTypeSafe(write, expectedType, false, processed);
+        return isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c);
     else
         return false;
 }
 
-bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed)
+bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &log, int &c)
     const
 {
     const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr;
@@ -2449,7 +2450,7 @@ bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr<LLVMInstruc
     auto argIns = arg->instruction;
 
     if (argIns && (argIns->type == LLVMInstruction::Type::ReadVariable || argIns->type == LLVMInstruction::Type::GetListItem))
-        return isVarOrListTypeSafe(argIns, expectedType, processed);
+        return isVarOrListTypeSafe(argIns, expectedType, log, c);
 
     // Check written type
     const bool typeMatches = (optimizeRegisterType(arg) == expectedType);
diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h
index 22fa1609..fb49fe64 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.h
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h
@@ -150,8 +150,8 @@ class LLVMCodeBuilder : public ICodeBuilder
         void reloadLists();
         void updateListDataPtr(const LLVMListPtr &listPtr);
         bool isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType) const;
-        bool isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &processed) const;
-        bool isVarOrListWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &processed) const;
+        bool isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, std::unordered_set<LLVMInstruction *> &log, int &c) const;
+        bool isVarOrListWriteResultTypeSafe(std::shared_ptr<LLVMInstruction> ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set<LLVMInstruction *> &log, int &c) const;
 
         LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args);
         LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {});

From b3117dc0ebdc72bd2d3800219867fca59c4b8b78 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 19 Jan 2025 00:29:33 +0100
Subject: [PATCH 254/259] LLVMCodeBuilder: Fix list type change before last
 write in loop

---
 .../engine/internal/llvm/llvmcodebuilder.cpp  | 19 +++++++++++++++----
 test/dev/llvm/llvmcodebuilder_test.cpp        | 13 +++++++++----
 2 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
index 04d23b39..0c0a9743 100644
--- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
+++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp
@@ -2356,7 +2356,7 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
     // Find previous write instruction in this, parent or child loop scope
     size_t index = it - instructions.begin();
 
-    if (index > 0) {
+    if (varPtr && index > 0) { // this is only needed for variables
         bool found = false;
 
         do {
@@ -2401,7 +2401,7 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
         checkScope = checkScope->parentScope;
     }
 
-    // Find n-th last write operation based on counter (may be in a parent or child scope)
+    // Get all write operations in all loop scopes (from the root loop scope)
     std::vector<std::shared_ptr<LLVMInstruction>> lastWrites;
 
     while (checkScope) {
@@ -2425,7 +2425,18 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
     if (c >= lastWrites.size())
         return true;
 
-    write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes
+    if (varPtr)
+        write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes
+    else {
+        // If this is a list instruction, check last write operations except current
+        for (long i = lastWrites.size() - c - 1; i >= 0; i--) { // Ignore last c writes
+            if (lastWrites[i] == ins)
+                continue;
+
+            if (!isVarOrListWriteResultTypeSafe(lastWrites[i], expectedType, false, log, c))
+                return false;
+        }
+    }
 
     bool safe = true;
 
@@ -2433,7 +2444,7 @@ bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr<LLVMInstruction> ins,
         safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, log, c);
 
     if (safe)
-        return isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c);
+        return write ? isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c) : true;
     else
         return false;
 }
diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp
index 1da6bf08..e107097b 100644
--- a/test/dev/llvm/llvmcodebuilder_test.cpp
+++ b/test/dev/llvm/llvmcodebuilder_test.cpp
@@ -5149,15 +5149,19 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6)
 
     createBuilder(&sprite, true);
 
+    m_builder->createListClear(globalList.get());
     m_builder->createListClear(localList.get());
 
-    CompilerValue *v = m_builder->addConstValue(5.25);
+    CompilerValue *v = m_builder->addConstValue(-156.07);
+    m_builder->createListAppend(globalList.get(), v);
+
+    v = m_builder->addConstValue(5.25);
     m_builder->createListAppend(localList.get(), v);
 
     v = m_builder->addConstValue(2);
     m_builder->beginRepeatLoop(v);
     {
-        // Type is unknown here because a list item with unknown type is added later (even though the list item has the original type assigned, it cannot be determined at compile time)
+        // Type is unknown here because a list item with unknown type is added later
         v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1));
         v = m_builder->addListItem(localList.get(), v);
         m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v });
@@ -5174,7 +5178,8 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6)
             v = m_builder->addConstValue(10);
             m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v);
 
-            v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0));
+            v = m_builder->createSub(m_builder->addListSize(globalList.get()), m_builder->addConstValue(1));
+            v = m_builder->addListItem(globalList.get(), v);
             m_builder->createListAppend(localList.get(), v);
 
             v = m_builder->addConstValue("test");
@@ -5188,7 +5193,7 @@ TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis6)
         "5.25\n"
         "0\n"
         "1\n"
-        "10\n"
+        "0\n"
         "0\n"
         "1\n";
 

From 5d6f27351502d36ee4f9cac7c2fb8d5cc3f8a67c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 26 Jan 2025 15:37:59 +0100
Subject: [PATCH 255/259] Add color() method to extensions

Resolves: #582
---
 include/scratchcpp/iextension.h          | 7 ++++---
 src/blocks/controlblocks.cpp             | 5 +++++
 src/blocks/controlblocks.h               | 1 +
 src/blocks/customblocks.cpp              | 5 +++++
 src/blocks/customblocks.h                | 1 +
 src/blocks/eventblocks.cpp               | 5 +++++
 src/blocks/eventblocks.h                 | 1 +
 src/blocks/listblocks.cpp                | 5 +++++
 src/blocks/listblocks.h                  | 1 +
 src/blocks/looksblocks.cpp               | 5 +++++
 src/blocks/looksblocks.h                 | 1 +
 src/blocks/motionblocks.cpp              | 5 +++++
 src/blocks/motionblocks.h                | 1 +
 src/blocks/operatorblocks.cpp            | 5 +++++
 src/blocks/operatorblocks.h              | 1 +
 src/blocks/sensingblocks.cpp             | 5 +++++
 src/blocks/sensingblocks.h               | 1 +
 src/blocks/soundblocks.cpp               | 5 +++++
 src/blocks/soundblocks.h                 | 1 +
 src/blocks/variableblocks.cpp            | 5 +++++
 src/blocks/variableblocks.h              | 1 +
 src/dev/blocks/controlblocks.cpp         | 5 +++++
 src/dev/blocks/controlblocks.h           | 1 +
 src/dev/blocks/customblocks.cpp          | 5 +++++
 src/dev/blocks/customblocks.h            | 1 +
 src/dev/blocks/eventblocks.cpp           | 5 +++++
 src/dev/blocks/eventblocks.h             | 1 +
 src/dev/blocks/listblocks.cpp            | 5 +++++
 src/dev/blocks/listblocks.h              | 1 +
 src/dev/blocks/looksblocks.cpp           | 5 +++++
 src/dev/blocks/looksblocks.h             | 1 +
 src/dev/blocks/motionblocks.cpp          | 5 +++++
 src/dev/blocks/motionblocks.h            | 1 +
 src/dev/blocks/operatorblocks.cpp        | 5 +++++
 src/dev/blocks/operatorblocks.h          | 1 +
 src/dev/blocks/sensingblocks.cpp         | 5 +++++
 src/dev/blocks/sensingblocks.h           | 1 +
 src/dev/blocks/soundblocks.cpp           | 5 +++++
 src/dev/blocks/soundblocks.h             | 1 +
 src/dev/blocks/variableblocks.cpp        | 5 +++++
 src/dev/blocks/variableblocks.h          | 1 +
 test/compiler/testextension.cpp          | 5 +++++
 test/compiler/testextension.h            | 1 +
 test/dev/test_api/testextension.cpp      | 5 +++++
 test/dev/test_api/testextension.h        | 1 +
 test/extensions/testextension.cpp        | 5 +++++
 test/extensions/testextension.h          | 1 +
 test/mocks/extensionmock.h               | 1 +
 test/scratch_classes/testextension.cpp   | 5 +++++
 test/scratch_classes/testextension.h     | 1 +
 test/scratchconfiguration/extension1.cpp | 5 +++++
 test/scratchconfiguration/extension1.h   | 1 +
 test/scratchconfiguration/extension2.cpp | 5 +++++
 test/scratchconfiguration/extension2.h   | 1 +
 test/scratchconfiguration/extension3.cpp | 5 +++++
 test/scratchconfiguration/extension3.h   | 1 +
 56 files changed, 167 insertions(+), 3 deletions(-)

diff --git a/include/scratchcpp/iextension.h b/include/scratchcpp/iextension.h
index a53b8356..0a4bff08 100644
--- a/include/scratchcpp/iextension.h
+++ b/include/scratchcpp/iextension.h
@@ -2,9 +2,7 @@
 
 #pragma once
 
-#include <string>
-
-#include "global.h"
+#include "value_functions.h"
 
 namespace libscratchcpp
 {
@@ -27,6 +25,9 @@ class LIBSCRATCHCPP_EXPORT IExtension
         /*! Returns the description of the extension. */
         virtual std::string description() const = 0;
 
+        /*! Returns the block color of the extension. */
+        virtual Rgb color() const = 0;
+
         /*! Override this method to register blocks. */
         virtual void registerBlocks(IEngine *engine) = 0;
 
diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp
index ab5aab69..aa4ebbce 100644
--- a/src/blocks/controlblocks.cpp
+++ b/src/blocks/controlblocks.cpp
@@ -25,6 +25,11 @@ std::string ControlBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb ControlBlocks::color() const
+{
+    return rgb(255, 171, 25);
+}
+
 void ControlBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/controlblocks.h b/src/blocks/controlblocks.h
index aa0c8710..623ab651 100644
--- a/src/blocks/controlblocks.h
+++ b/src/blocks/controlblocks.h
@@ -43,6 +43,7 @@ class ControlBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/blocks/customblocks.cpp b/src/blocks/customblocks.cpp
index aee3b757..8066a025 100644
--- a/src/blocks/customblocks.cpp
+++ b/src/blocks/customblocks.cpp
@@ -20,6 +20,11 @@ std::string CustomBlocks::description() const
     return name();
 }
 
+Rgb CustomBlocks::color() const
+{
+    return rgb(255, 102, 128);
+}
+
 void CustomBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/customblocks.h b/src/blocks/customblocks.h
index a40e37c3..23482886 100644
--- a/src/blocks/customblocks.h
+++ b/src/blocks/customblocks.h
@@ -24,6 +24,7 @@ class CustomBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp
index 757e2bda..85ec9235 100644
--- a/src/blocks/eventblocks.cpp
+++ b/src/blocks/eventblocks.cpp
@@ -30,6 +30,11 @@ std::string libscratchcpp::EventBlocks::description() const
     return "Event blocks";
 }
 
+Rgb EventBlocks::color() const
+{
+    return rgb(255, 191, 0);
+}
+
 void EventBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h
index e4d28f4a..9ddca5b6 100644
--- a/src/blocks/eventblocks.h
+++ b/src/blocks/eventblocks.h
@@ -38,6 +38,7 @@ class EventBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/blocks/listblocks.cpp b/src/blocks/listblocks.cpp
index 135dd0bd..4302c3bc 100644
--- a/src/blocks/listblocks.cpp
+++ b/src/blocks/listblocks.cpp
@@ -23,6 +23,11 @@ std::string ListBlocks::description() const
     return "List blocks";
 }
 
+Rgb ListBlocks::color() const
+{
+    return rgb(255, 102, 26);
+}
+
 void ListBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/listblocks.h b/src/blocks/listblocks.h
index 69e49aa3..ed621314 100644
--- a/src/blocks/listblocks.h
+++ b/src/blocks/listblocks.h
@@ -28,6 +28,7 @@ class ListBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp
index f142f34c..4a5ddf31 100644
--- a/src/blocks/looksblocks.cpp
+++ b/src/blocks/looksblocks.cpp
@@ -42,6 +42,11 @@ std::string LooksBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb LooksBlocks::color() const
+{
+    return rgb(153, 102, 255);
+}
+
 void LooksBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h
index 8881d7eb..eca733de 100644
--- a/src/blocks/looksblocks.h
+++ b/src/blocks/looksblocks.h
@@ -60,6 +60,7 @@ class LooksBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
         void onInit(IEngine *engine) override;
diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp
index c5a96004..62fd28ae 100644
--- a/src/blocks/motionblocks.cpp
+++ b/src/blocks/motionblocks.cpp
@@ -29,6 +29,11 @@ std::string MotionBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb MotionBlocks::color() const
+{
+    return rgb(76, 151, 255);
+}
+
 void MotionBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h
index b545822e..b86e9481 100644
--- a/src/blocks/motionblocks.h
+++ b/src/blocks/motionblocks.h
@@ -45,6 +45,7 @@ class MotionBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
         void onInit(IEngine *engine) override;
diff --git a/src/blocks/operatorblocks.cpp b/src/blocks/operatorblocks.cpp
index 376ae27a..567d4a15 100644
--- a/src/blocks/operatorblocks.cpp
+++ b/src/blocks/operatorblocks.cpp
@@ -18,6 +18,11 @@ std::string OperatorBlocks::description() const
     return "Operator blocks";
 }
 
+Rgb OperatorBlocks::color() const
+{
+    return rgb(89, 192, 89);
+}
+
 void OperatorBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/operatorblocks.h b/src/blocks/operatorblocks.h
index e9dd932a..f2f5b729 100644
--- a/src/blocks/operatorblocks.h
+++ b/src/blocks/operatorblocks.h
@@ -55,6 +55,7 @@ class OperatorBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp
index af1349ed..f5b18ce7 100644
--- a/src/blocks/sensingblocks.cpp
+++ b/src/blocks/sensingblocks.cpp
@@ -34,6 +34,11 @@ std::string SensingBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb SensingBlocks::color() const
+{
+    return rgb(92, 177, 214);
+}
+
 void SensingBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h
index 8153a18b..5b3fc638 100644
--- a/src/blocks/sensingblocks.h
+++ b/src/blocks/sensingblocks.h
@@ -60,6 +60,7 @@ class SensingBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
         void onInit(IEngine *engine) override;
diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp
index 6cc2aa7b..f001fa7b 100644
--- a/src/blocks/soundblocks.cpp
+++ b/src/blocks/soundblocks.cpp
@@ -34,6 +34,11 @@ std::string SoundBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb SoundBlocks::color() const
+{
+    return rgb(207, 99, 207);
+}
+
 void SoundBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h
index d31b8a62..0535eb73 100644
--- a/src/blocks/soundblocks.h
+++ b/src/blocks/soundblocks.h
@@ -37,6 +37,7 @@ class SoundBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
         void onInit(IEngine *engine) override;
diff --git a/src/blocks/variableblocks.cpp b/src/blocks/variableblocks.cpp
index 81961e49..17236a30 100644
--- a/src/blocks/variableblocks.cpp
+++ b/src/blocks/variableblocks.cpp
@@ -23,6 +23,11 @@ std::string VariableBlocks::description() const
     return "Variable blocks";
 }
 
+Rgb VariableBlocks::color() const
+{
+    return rgb(255, 140, 26);
+}
+
 void VariableBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
diff --git a/src/blocks/variableblocks.h b/src/blocks/variableblocks.h
index 2372660b..f4b2922c 100644
--- a/src/blocks/variableblocks.h
+++ b/src/blocks/variableblocks.h
@@ -27,6 +27,7 @@ class VariableBlocks : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp
index 9567f69f..0ca0524d 100644
--- a/src/dev/blocks/controlblocks.cpp
+++ b/src/dev/blocks/controlblocks.cpp
@@ -26,6 +26,11 @@ std::string ControlBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb ControlBlocks::color() const
+{
+    return rgb(255, 171, 25);
+}
+
 void ControlBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "control_forever", &compileForever);
diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h
index 17850824..0f44982c 100644
--- a/src/dev/blocks/controlblocks.h
+++ b/src/dev/blocks/controlblocks.h
@@ -12,6 +12,7 @@ class ControlBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/dev/blocks/customblocks.cpp b/src/dev/blocks/customblocks.cpp
index e78048bc..655e4d3b 100644
--- a/src/dev/blocks/customblocks.cpp
+++ b/src/dev/blocks/customblocks.cpp
@@ -20,6 +20,11 @@ std::string CustomBlocks::description() const
     return name();
 }
 
+Rgb CustomBlocks::color() const
+{
+    return rgb(255, 102, 128);
+}
+
 void CustomBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "procedures_definition", [](Compiler *) -> CompilerValue * { return nullptr; });
diff --git a/src/dev/blocks/customblocks.h b/src/dev/blocks/customblocks.h
index 55572ba9..b2a2613f 100644
--- a/src/dev/blocks/customblocks.h
+++ b/src/dev/blocks/customblocks.h
@@ -12,6 +12,7 @@ class CustomBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp
index a2b18b3b..ba4d2663 100644
--- a/src/dev/blocks/eventblocks.cpp
+++ b/src/dev/blocks/eventblocks.cpp
@@ -24,6 +24,11 @@ std::string libscratchcpp::EventBlocks::description() const
     return "Event blocks";
 }
 
+Rgb EventBlocks::color() const
+{
+    return rgb(255, 191, 0);
+}
+
 void EventBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject);
diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h
index 34454ebc..512bae0c 100644
--- a/src/dev/blocks/eventblocks.h
+++ b/src/dev/blocks/eventblocks.h
@@ -12,6 +12,7 @@ class EventBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp
index 653875e4..64b44135 100644
--- a/src/dev/blocks/listblocks.cpp
+++ b/src/dev/blocks/listblocks.cpp
@@ -20,6 +20,11 @@ std::string ListBlocks::description() const
     return "List blocks";
 }
 
+Rgb ListBlocks::color() const
+{
+    return rgb(255, 102, 26);
+}
+
 void ListBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "data_addtolist", &compileAddToList);
diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h
index 3a8d0126..02dbd79c 100644
--- a/src/dev/blocks/listblocks.h
+++ b/src/dev/blocks/listblocks.h
@@ -14,6 +14,7 @@ class ListBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/dev/blocks/looksblocks.cpp b/src/dev/blocks/looksblocks.cpp
index 0d471fb0..8da59e7a 100644
--- a/src/dev/blocks/looksblocks.cpp
+++ b/src/dev/blocks/looksblocks.cpp
@@ -14,6 +14,11 @@ std::string LooksBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb LooksBlocks::color() const
+{
+    return rgb(153, 102, 255);
+}
+
 void LooksBlocks::registerBlocks(IEngine *engine)
 {
 }
diff --git a/src/dev/blocks/looksblocks.h b/src/dev/blocks/looksblocks.h
index 8bfa5f18..b7529754 100644
--- a/src/dev/blocks/looksblocks.h
+++ b/src/dev/blocks/looksblocks.h
@@ -12,6 +12,7 @@ class LooksBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 };
diff --git a/src/dev/blocks/motionblocks.cpp b/src/dev/blocks/motionblocks.cpp
index e44a673c..a249cdff 100644
--- a/src/dev/blocks/motionblocks.cpp
+++ b/src/dev/blocks/motionblocks.cpp
@@ -14,6 +14,11 @@ std::string MotionBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb MotionBlocks::color() const
+{
+    return rgb(76, 151, 255);
+}
+
 void MotionBlocks::registerBlocks(IEngine *engine)
 {
 }
diff --git a/src/dev/blocks/motionblocks.h b/src/dev/blocks/motionblocks.h
index 125b4073..44d7c0bd 100644
--- a/src/dev/blocks/motionblocks.h
+++ b/src/dev/blocks/motionblocks.h
@@ -12,6 +12,7 @@ class MotionBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 };
diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp
index b962d3c7..508d41a1 100644
--- a/src/dev/blocks/operatorblocks.cpp
+++ b/src/dev/blocks/operatorblocks.cpp
@@ -21,6 +21,11 @@ std::string OperatorBlocks::description() const
     return "Operator blocks";
 }
 
+Rgb OperatorBlocks::color() const
+{
+    return rgb(89, 192, 89);
+}
+
 void OperatorBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "operator_add", &compileAdd);
diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h
index 1804e931..647512e6 100644
--- a/src/dev/blocks/operatorblocks.h
+++ b/src/dev/blocks/operatorblocks.h
@@ -14,6 +14,7 @@ class OperatorBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/src/dev/blocks/sensingblocks.cpp b/src/dev/blocks/sensingblocks.cpp
index 42896ecb..8e3b18ca 100644
--- a/src/dev/blocks/sensingblocks.cpp
+++ b/src/dev/blocks/sensingblocks.cpp
@@ -14,6 +14,11 @@ std::string SensingBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb SensingBlocks::color() const
+{
+    return rgb(92, 177, 214);
+}
+
 void SensingBlocks::registerBlocks(IEngine *engine)
 {
 }
diff --git a/src/dev/blocks/sensingblocks.h b/src/dev/blocks/sensingblocks.h
index ab934fb0..9a0f367f 100644
--- a/src/dev/blocks/sensingblocks.h
+++ b/src/dev/blocks/sensingblocks.h
@@ -12,6 +12,7 @@ class SensingBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 };
diff --git a/src/dev/blocks/soundblocks.cpp b/src/dev/blocks/soundblocks.cpp
index 4a440a9a..df7611ab 100644
--- a/src/dev/blocks/soundblocks.cpp
+++ b/src/dev/blocks/soundblocks.cpp
@@ -14,6 +14,11 @@ std::string SoundBlocks::description() const
     return name() + " blocks";
 }
 
+Rgb SoundBlocks::color() const
+{
+    return rgb(207, 99, 207);
+}
+
 void SoundBlocks::registerBlocks(IEngine *engine)
 {
 }
diff --git a/src/dev/blocks/soundblocks.h b/src/dev/blocks/soundblocks.h
index afae6c9c..f605c7b6 100644
--- a/src/dev/blocks/soundblocks.h
+++ b/src/dev/blocks/soundblocks.h
@@ -12,6 +12,7 @@ class SoundBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 };
diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp
index 177d8dab..9e77c7bf 100644
--- a/src/dev/blocks/variableblocks.cpp
+++ b/src/dev/blocks/variableblocks.cpp
@@ -21,6 +21,11 @@ std::string VariableBlocks::description() const
     return "Variable blocks";
 }
 
+Rgb VariableBlocks::color() const
+{
+    return rgb(255, 140, 26);
+}
+
 void VariableBlocks::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "data_variable", &compileVariable);
diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h
index 9cb0bcc4..423085d3 100644
--- a/src/dev/blocks/variableblocks.h
+++ b/src/dev/blocks/variableblocks.h
@@ -12,6 +12,7 @@ class VariableBlocks : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/test/compiler/testextension.cpp b/test/compiler/testextension.cpp
index cb001061..ce26bbc3 100644
--- a/test/compiler/testextension.cpp
+++ b/test/compiler/testextension.cpp
@@ -18,6 +18,11 @@ std::string TestExtension::description() const
     return "";
 }
 
+Rgb TestExtension::color() const
+{
+    return rgb(0, 0, 0);
+}
+
 void TestExtension::registerBlocks(IEngine *engine)
 {
     engine->addInput(this, "INPUT1", INPUT1);
diff --git a/test/compiler/testextension.h b/test/compiler/testextension.h
index c7bdf102..6efc819d 100644
--- a/test/compiler/testextension.h
+++ b/test/compiler/testextension.h
@@ -25,6 +25,7 @@ class TestExtension : public IExtension
 
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/test/dev/test_api/testextension.cpp b/test/dev/test_api/testextension.cpp
index c637e522..24eebec2 100644
--- a/test/dev/test_api/testextension.cpp
+++ b/test/dev/test_api/testextension.cpp
@@ -21,6 +21,11 @@ std::string TestExtension::description() const
     return "";
 }
 
+Rgb TestExtension::color() const
+{
+    return rgb(0, 0, 0);
+}
+
 void TestExtension::registerBlocks(IEngine *engine)
 {
     engine->addCompileFunction(this, "test_simple", &compileSimple);
diff --git a/test/dev/test_api/testextension.h b/test/dev/test_api/testextension.h
index e69bbcdd..12f4e729 100644
--- a/test/dev/test_api/testextension.h
+++ b/test/dev/test_api/testextension.h
@@ -10,6 +10,7 @@ class TestExtension : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 
diff --git a/test/extensions/testextension.cpp b/test/extensions/testextension.cpp
index d7a8dd5b..5d122d48 100644
--- a/test/extensions/testextension.cpp
+++ b/test/extensions/testextension.cpp
@@ -14,6 +14,11 @@ std::string TestExtension::description() const
     return "Test extension";
 }
 
+Rgb TestExtension::color() const
+{
+    return rgb(0, 0, 0);
+}
+
 void TestExtension::registerBlocks(IEngine *engine)
 {
     engine->clear();
diff --git a/test/extensions/testextension.h b/test/extensions/testextension.h
index 7bad056d..7e89e77d 100644
--- a/test/extensions/testextension.h
+++ b/test/extensions/testextension.h
@@ -10,6 +10,7 @@ class TestExtension : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
         void onInit(IEngine *engine) override;
diff --git a/test/mocks/extensionmock.h b/test/mocks/extensionmock.h
index 19f47d6e..e0750c37 100644
--- a/test/mocks/extensionmock.h
+++ b/test/mocks/extensionmock.h
@@ -10,6 +10,7 @@ class ExtensionMock : public IExtension
     public:
         MOCK_METHOD(std::string, name, (), (const, override));
         MOCK_METHOD(std::string, description, (), (const, override));
+        MOCK_METHOD(Rgb, color, (), (const, override));
 
         MOCK_METHOD(void, registerBlocks, (IEngine *), (override));
         MOCK_METHOD(void, onInit, (IEngine *), (override));
diff --git a/test/scratch_classes/testextension.cpp b/test/scratch_classes/testextension.cpp
index cd848ca6..9d21e10e 100644
--- a/test/scratch_classes/testextension.cpp
+++ b/test/scratch_classes/testextension.cpp
@@ -14,6 +14,11 @@ std::string TestExtension::description() const
     return "";
 }
 
+Rgb TestExtension::color() const
+{
+    return rgb(0, 0, 0);
+}
+
 void TestExtension::registerBlocks(IEngine *engine)
 {
 }
diff --git a/test/scratch_classes/testextension.h b/test/scratch_classes/testextension.h
index 88162cba..d15b2094 100644
--- a/test/scratch_classes/testextension.h
+++ b/test/scratch_classes/testextension.h
@@ -10,6 +10,7 @@ class TestExtension : public IExtension
     public:
         std::string name() const override;
         std::string description() const override;
+        Rgb color() const override;
 
         void registerBlocks(IEngine *engine) override;
 };
diff --git a/test/scratchconfiguration/extension1.cpp b/test/scratchconfiguration/extension1.cpp
index 2e8fa183..b49f2b0e 100644
--- a/test/scratchconfiguration/extension1.cpp
+++ b/test/scratchconfiguration/extension1.cpp
@@ -6,3 +6,8 @@ std::string Extension1::name() const
 {
     return "ext 1";
 }
+
+Rgb Extension1::color() const
+{
+    return rgb(0, 0, 0);
+}
diff --git a/test/scratchconfiguration/extension1.h b/test/scratchconfiguration/extension1.h
index f7f8009e..ea08ea87 100644
--- a/test/scratchconfiguration/extension1.h
+++ b/test/scratchconfiguration/extension1.h
@@ -9,6 +9,7 @@ class Extension1 : public ExtensionBase
 {
     public:
         std::string name() const override;
+        Rgb color() const override;
 };
 
 } // namespace libscratchcpp
diff --git a/test/scratchconfiguration/extension2.cpp b/test/scratchconfiguration/extension2.cpp
index 5c58c11e..5efe0561 100644
--- a/test/scratchconfiguration/extension2.cpp
+++ b/test/scratchconfiguration/extension2.cpp
@@ -6,3 +6,8 @@ std::string Extension2::name() const
 {
     return "ext 2";
 }
+
+Rgb Extension2::color() const
+{
+    return rgb(0, 0, 0);
+}
diff --git a/test/scratchconfiguration/extension2.h b/test/scratchconfiguration/extension2.h
index 22caf7f1..a6e03cd8 100644
--- a/test/scratchconfiguration/extension2.h
+++ b/test/scratchconfiguration/extension2.h
@@ -9,6 +9,7 @@ class Extension2 : public ExtensionBase
 {
     public:
         std::string name() const override;
+        Rgb color() const override;
 };
 
 } // namespace libscratchcpp
diff --git a/test/scratchconfiguration/extension3.cpp b/test/scratchconfiguration/extension3.cpp
index fb28af9b..021524f1 100644
--- a/test/scratchconfiguration/extension3.cpp
+++ b/test/scratchconfiguration/extension3.cpp
@@ -6,3 +6,8 @@ std::string Extension3::name() const
 {
     return "ext 3";
 }
+
+Rgb Extension3::color() const
+{
+    return rgb(0, 0, 0);
+}
diff --git a/test/scratchconfiguration/extension3.h b/test/scratchconfiguration/extension3.h
index d434d754..5ebe8c4d 100644
--- a/test/scratchconfiguration/extension3.h
+++ b/test/scratchconfiguration/extension3.h
@@ -9,6 +9,7 @@ class Extension3 : public ExtensionBase
 {
     public:
         std::string name() const override;
+        Rgb color() const override;
 };
 
 } // namespace libscratchcpp

From a053380920c880541248147078a82e705c01a284 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 28 Jan 2025 14:41:01 +0100
Subject: [PATCH 256/259] audio: Fix a race condition in miniaudio

https://github.com/mackron/miniaudio/issues/932
---
 .../internal/thirdparty/miniaudio/miniaudio.c   | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/src/audio/internal/thirdparty/miniaudio/miniaudio.c b/src/audio/internal/thirdparty/miniaudio/miniaudio.c
index 651843b2..077c396c 100644
--- a/src/audio/internal/thirdparty/miniaudio/miniaudio.c
+++ b/src/audio/internal/thirdparty/miniaudio/miniaudio.c
@@ -18616,15 +18616,20 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra
 static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap)
 {
     static int g_StreamCounter = 0;
+    static ma_mutex g_StreamCounterMutex;
     char actualStreamName[256];
 
-    if (pStreamName != NULL) {
-        ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1);
-    } else {
-        ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:");
-        ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10);  /* 8 = strlen("miniaudio:") */
+    ma_mutex_lock(&g_StreamCounterMutex);
+    {
+        if (pStreamName != NULL) {
+            ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1);
+        } else {
+            ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:");
+            ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10);  /* 8 = strlen("miniaudio:") */
+        }
+        g_StreamCounter += 1;
     }
-    g_StreamCounter += 1;
+    ma_mutex_unlock(&g_StreamCounterMutex);
 
     return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap);
 }

From 7c1e08a5c2f0bc192e66c432fb5800d716db1bb2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 25 Jan 2025 22:47:57 +0100
Subject: [PATCH 257/259] Refactor CMake configuration

---
 CMakeLists.txt  | 28 ++++++++++++++++++----------
 build/zip.cmake |  9 +++++++++
 2 files changed, 27 insertions(+), 10 deletions(-)
 create mode 100644 build/zip.cmake

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8ca8c1aa..bd773c01 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -95,26 +95,27 @@ else()
 endif()
 
 include(FetchContent)
-set(ZIP_SRC thirdparty/zip/src)
-set(UTFCPP_SRC thirdparty/utfcpp/source)
 
-add_library(zip SHARED
-	${ZIP_SRC}/zip.c
-	${ZIP_SRC}/zip.h
-	${ZIP_SRC}/miniz.h
-)
-target_include_directories(scratchcpp PUBLIC ${ZIP_SRC})
+# zip
+include(build/zip.cmake)
+target_link_libraries(scratchcpp PRIVATE zip)
 
+# utfcpp
+set(UTFCPP_SRC thirdparty/utfcpp/source)
 target_include_directories(scratchcpp PUBLIC ${UTFCPP_SRC})
+
+# spimpl
 include_directories(thirdparty/spimpl)
 
+# JSON
 FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
 FetchContent_MakeAvailable(json)
-
 target_link_libraries(scratchcpp PRIVATE nlohmann_json::nlohmann_json)
-target_link_libraries(scratchcpp PRIVATE zip)
+
+# Audio
 target_link_libraries(scratchcpp PRIVATE scratchcpp-audio)
 
+# Network
 if (LIBSCRATCHCPP_NETWORK_SUPPORT)
     FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git
                          GIT_TAG 225b7454877805f089b3895260438e929bd6d123) # 09-22-2024
@@ -123,18 +124,25 @@ if (LIBSCRATCHCPP_NETWORK_SUPPORT)
     target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_NETWORK_SUPPORT)
 endif()
 
+# LLVM
 if (LIBSCRATCHCPP_USE_LLVM)
     include(build/HunterPackages.cmake)
     include(build/LLVM.cmake)
     target_link_libraries(scratchcpp PRIVATE LLVM)
 endif()
 
+if(LIBSCRATCHCPP_PRINT_LLVM_IR)
+    target_compile_definitions(scratchcpp PRIVATE PRINT_LLVM_IR)
+endif()
+
+# Macros
 target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_LIBRARY)
 target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION="${PROJECT_VERSION}")
 target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MAJOR=${PROJECT_VERSION_MAJOR})
 target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MINOR=${PROJECT_VERSION_MINOR})
 target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_PATCH=${PROJECT_VERSION_PATCH})
 
+# Unit tests
 if (LIBSCRATCHCPP_BUILD_UNIT_TESTS)
     enable_testing()
     add_subdirectory(test)
diff --git a/build/zip.cmake b/build/zip.cmake
new file mode 100644
index 00000000..cf970fb8
--- /dev/null
+++ b/build/zip.cmake
@@ -0,0 +1,9 @@
+set(ZIP_SRC ${PROJECT_SOURCE_DIR}/thirdparty/zip/src)
+
+add_library(zip SHARED
+    ${ZIP_SRC}/zip.c
+    ${ZIP_SRC}/zip.h
+    ${ZIP_SRC}/miniz.h
+)
+
+target_include_directories(zip PUBLIC ${ZIP_SRC})

From 30ca4d39b77648e35cee24feeff828d0a8abaf8e Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 25 Jan 2025 22:59:02 +0100
Subject: [PATCH 258/259] Configure CMake installation

---
 CMakeLists.txt  | 1 +
 build/zip.cmake | 1 +
 2 files changed, 2 insertions(+)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index bd773c01..810a5382 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,6 +21,7 @@ add_library(scratchcpp SHARED)
 add_subdirectory(src)
 include_directories(src) # TODO: Remove this line
 include_directories(include)
+install(TARGETS scratchcpp DESTINATION lib)
 
 if (LIBSCRATCHCPP_COMPUTED_GOTO)
 	target_compile_definitions(scratchcpp PRIVATE ENABLE_COMPUTED_GOTO)
diff --git a/build/zip.cmake b/build/zip.cmake
index cf970fb8..1165aba8 100644
--- a/build/zip.cmake
+++ b/build/zip.cmake
@@ -7,3 +7,4 @@ add_library(zip SHARED
 )
 
 target_include_directories(zip PUBLIC ${ZIP_SRC})
+install(TARGETS zip DESTINATION lib)

From a5c4dbe9acc177af46c1e70c5a5817f55faed58b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 28 Jan 2025 15:21:32 +0100
Subject: [PATCH 259/259] Link compiler test against zip library

---
 test/compiler/CMakeLists.txt | 1 +
 1 file changed, 1 insertion(+)

diff --git a/test/compiler/CMakeLists.txt b/test/compiler/CMakeLists.txt
index 9dc335df..0b7d501a 100644
--- a/test/compiler/CMakeLists.txt
+++ b/test/compiler/CMakeLists.txt
@@ -9,6 +9,7 @@ target_link_libraries(
   compiler_test
   GTest::gtest_main
   scratchcpp
+  zip
   nlohmann_json::nlohmann_json
 )
 
