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 01/20] 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(name, Input::Type::Shadow); input->setPrimaryValue(entityName); input->primaryValue()->setValuePtr(entity); @@ -188,7 +192,11 @@ void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptrlastBlock) 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(name, Value(), entity); impl->lastBlock->addField(field); } @@ -203,8 +211,19 @@ std::shared_ptr ScriptBuilder::currentBlock() if (!impl->lastBlock) return nullptr; - if (!impl->lastBlock->compileFunction()) - build(std::make_shared()); + if (!impl->lastBlock->compileFunction()) { + auto target = std::make_shared(); + 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 lastBlock; std::vector> blocks; std::vector> inputBlocks; + std::vector> 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 #include #include -#include +#include #include "../../common.h" #include "testextension.h" @@ -183,34 +183,42 @@ TEST_F(ScriptBuilderTest, AddDropdownField) TEST_F(ScriptBuilderTest, AddEntityInput) { - auto broadcast = std::make_shared("", ""); - m_engine->setBroadcasts({ broadcast }); + auto var = std::make_shared("", ""); + 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("", ""); - m_engine->setBroadcasts({ broadcast }); + auto var = std::make_shared("", ""); + 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 02/20] 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 +#include +#include +#include +#include + #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.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 +#include +#include +#include +#include #include #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(); } + void SetUp() override + { + m_extension = std::make_unique(); + m_engine = m_project.engine().get(); + m_extension->registerBlocks(m_engine); + } std::unique_ptr m_extension; + Project m_project; + IEngine *m_engine = nullptr; EngineMock m_engineMock; }; + +TEST_F(ListBlocksTest, AddToList) +{ + auto target = std::make_shared(); + auto list1 = std::make_shared("", ""); + target->addList(list1); + auto list2 = std::make_shared("", ""); + 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 03/20] 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::infinity(); + ASSERT_EQ(v.toInt(), 0); + v = -std::numeric_limits::infinity(); + ASSERT_EQ(v.toInt(), 0); + v = std::numeric_limits::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::infinity(); + ASSERT_EQ(v.toLong(), 0); + v = -std::numeric_limits::infinity(); + ASSERT_EQ(v.toLong(), 0); + v = std::numeric_limits::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 04/20] 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::infinity(); const double nan = std::numeric_limits::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 05/20] 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 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 06/20] 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 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 +#include + +#include "compilerlocalvariable_p.h" + +using namespace libscratchcpp; + +CompilerLocalVariable::CompilerLocalVariable(CompilerValue *ptr) : + impl(spimpl::make_unique_impl(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 + +#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 +#include +#include + +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 07/20] 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 #include #include +#include #include "llvmcodebuilder.h" #include "llvmexecutablecode.h" @@ -511,6 +512,64 @@ std::shared_ptr 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(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 m_instructions; std::vector> m_regs; + std::vector> 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 08/20] 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->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 09/20] 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 #include +#include #include #include #include @@ -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("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->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->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 10/20] 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(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 #include #include +#include +#include +#include +#include #include +#include #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(); + + auto list1 = std::make_shared("", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("", ""); + 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("", ""); + 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) { + 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 11/20] 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.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(); + + auto list = std::make_shared("", ""); + 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 12/20] 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(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(); + + auto list1 = std::make_shared("", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("", ""); + 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) { + 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 13/20] 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(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(); + + auto list1 = std::make_shared("", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("", ""); + 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) { + 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 14/20] 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 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(TYPE_MAP[value.type()], value); auto reg = std::reinterpret_pointer_cast(constReg); - return static_cast(addReg(reg)); + return static_cast(static_cast(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 reg) +LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr 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 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 reg); + LLVMRegister *addReg(std::shared_ptr 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 15/20] 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 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 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 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 LLVMCodeBuilder::finalize() assert(step.args.size() == 1); const auto ® = 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 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 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 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 LLVMCodeBuilder::finalize() const auto ® = 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 LLVMCodeBuilder::finalize() const auto ® = 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 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 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 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 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 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 m_heap; + std::vector> m_heap; // scopes std::shared_ptr 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 16/20] 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); std::shared_ptr currentBlock(); + std::shared_ptr 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(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_ptrsetParent(impl->lastBlock); while (block) { block->setId(std::to_string(impl->blockId++)); @@ -112,7 +107,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptrparent(); auto next = block->next(); - if (parent) + if (parent && block != valueBlock) parent->setNext(block); if (next) @@ -228,6 +223,24 @@ std::shared_ptr ScriptBuilder::currentBlock() return impl->lastBlock; } +/*! Removes the current block from the script and returns it. Can be used in inputs later. */ +std::shared_ptr 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 17/20] 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(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 #include #include +#include +#include +#include #include #include #include #include #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(); m_engine = m_project.engine().get(); m_extension->registerBlocks(m_engine); + registerBlocks(m_engine, m_extension.get()); } std::unique_ptr 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(); + + 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); + + auto addTest = [&builder](const Value &index, std::shared_ptr 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(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 18/20] 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(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(); + + auto list = std::make_shared("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) { + 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 19/20] 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(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(); + + auto list1 = std::make_shared("list1", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("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) { + 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 20/20] 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(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(); + + auto list = std::make_shared("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) { + 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"); +}