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");
+}