From 60cc93fb2345345fca5f32b51991530a72a200b7 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:50:32 +0100 Subject: [PATCH 01/25] Implement operator_add --- src/dev/blocks/operatorblocks.cpp | 9 +++++++ src/dev/blocks/operatorblocks.h | 3 +++ test/dev/blocks/operator_blocks_test.cpp | 34 +++++++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 8d4cbb92..6ee18e57 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -1,5 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include + #include "operatorblocks.h" using namespace libscratchcpp; @@ -16,4 +19,10 @@ std::string OperatorBlocks::description() const void OperatorBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "operator_add", &compileAdd); +} + +CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) +{ + return compiler->createAdd(compiler->addInput("NUM1"), compiler->addInput("NUM2")); } diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index af5bd7b5..8c4c924f 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -14,6 +14,9 @@ class OperatorBlocks : public IExtension std::string description() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileAdd(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index f29aacec..e2fbb912 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -1,15 +1,47 @@ +#include +#include +#include +#include +#include #include #include "../common.h" #include "dev/blocks/operatorblocks.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; class OperatorBlocksTest : public testing::Test { public: - void SetUp() override { m_extension = std::make_unique(); } + 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(OperatorBlocksTest, Add) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_add"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(Value(values[0]), 8.2); +} From 8ee476830f0b0a9f73ba36929779260bc4cae382 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 22 Dec 2024 16:56:31 +0100 Subject: [PATCH 02/25] Implement operator_subtract --- src/dev/blocks/operatorblocks.cpp | 6 ++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 6ee18e57..6be5c594 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -20,9 +20,15 @@ std::string OperatorBlocks::description() const void OperatorBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "operator_add", &compileAdd); + engine->addCompileFunction(this, "operator_subtract", &compileSubtract); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) { return compiler->createAdd(compiler->addInput("NUM1"), compiler->addInput("NUM2")); } + +CompilerValue *OperatorBlocks::compileSubtract(Compiler *compiler) +{ + return compiler->createSub(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 8c4c924f..30c7a426 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -17,6 +17,7 @@ class OperatorBlocks : public IExtension private: static CompilerValue *compileAdd(Compiler *compiler); + static CompilerValue *compileSubtract(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index e2fbb912..21e2a0e6 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -45,3 +45,22 @@ TEST_F(OperatorBlocksTest, Add) ASSERT_EQ(valueList->size(), 1); ASSERT_EQ(Value(values[0]), 8.2); } + +TEST_F(OperatorBlocksTest, Subtract) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_subtract"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(Value(values[0]), 3.2); +} From 19b35d1893054e6fc8071f726940b9d7c9479356 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:03:10 +0100 Subject: [PATCH 03/25] Implement operator_multiply --- src/dev/blocks/operatorblocks.cpp | 6 ++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 6be5c594..69e8f17f 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -21,6 +21,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) { engine->addCompileFunction(this, "operator_add", &compileAdd); engine->addCompileFunction(this, "operator_subtract", &compileSubtract); + engine->addCompileFunction(this, "operator_multiply", &compileMultiply); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -32,3 +33,8 @@ CompilerValue *OperatorBlocks::compileSubtract(Compiler *compiler) { return compiler->createSub(compiler->addInput("NUM1"), compiler->addInput("NUM2")); } + +CompilerValue *OperatorBlocks::compileMultiply(Compiler *compiler) +{ + return compiler->createMul(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 30c7a426..e155c726 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -18,6 +18,7 @@ class OperatorBlocks : public IExtension private: static CompilerValue *compileAdd(Compiler *compiler); static CompilerValue *compileSubtract(Compiler *compiler); + static CompilerValue *compileMultiply(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 21e2a0e6..6c28a02c 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -64,3 +64,22 @@ TEST_F(OperatorBlocksTest, Subtract) ASSERT_EQ(valueList->size(), 1); ASSERT_EQ(Value(values[0]), 3.2); } + +TEST_F(OperatorBlocksTest, Multiply) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_multiply"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(Value(values[0]), 14.25); +} From 7b9df371a4763fb1e3d7deaf1746b105ef996313 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Sun, 22 Dec 2024 17:06:07 +0100 Subject: [PATCH 04/25] Implement operator_divide --- src/dev/blocks/operatorblocks.cpp | 6 ++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 69e8f17f..f98a924b 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -22,6 +22,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_add", &compileAdd); engine->addCompileFunction(this, "operator_subtract", &compileSubtract); engine->addCompileFunction(this, "operator_multiply", &compileMultiply); + engine->addCompileFunction(this, "operator_divide", &compileDivide); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -38,3 +39,8 @@ CompilerValue *OperatorBlocks::compileMultiply(Compiler *compiler) { return compiler->createMul(compiler->addInput("NUM1"), compiler->addInput("NUM2")); } + +CompilerValue *OperatorBlocks::compileDivide(Compiler *compiler) +{ + return compiler->createDiv(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index e155c726..89b7a1d6 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -19,6 +19,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileAdd(Compiler *compiler); static CompilerValue *compileSubtract(Compiler *compiler); static CompilerValue *compileMultiply(Compiler *compiler); + static CompilerValue *compileDivide(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 6c28a02c..13282adb 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -83,3 +83,22 @@ TEST_F(OperatorBlocksTest, Multiply) ASSERT_EQ(valueList->size(), 1); ASSERT_EQ(Value(values[0]), 14.25); } + +TEST_F(OperatorBlocksTest, Divide) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_divide"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 2.28); +} From b611cb1290a1090c60140848f6618c37bc55ba29 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:33:06 +0100 Subject: [PATCH 05/25] LLVMCodeBuilder: Disable no infinity fast math flag --- src/dev/engine/internal/llvm/llvmcodebuilder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 069f7bd9..959871ee 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -53,6 +53,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Set fast math flags llvm::FastMathFlags fmf; fmf.setFast(true); + fmf.setNoInfs(false); fmf.setNoNaNs(false); fmf.setNoSignedZeros(false); m_builder.setFastMathFlags(fmf); From 04793097715fea412170509e0f89cf76c334fb64 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:11:55 +0100 Subject: [PATCH 06/25] Add value_doubleIsInt() function --- include/scratchcpp/value_functions.h | 2 ++ src/scratch/value_functions.cpp | 20 +++++++++++++------- test/scratch_classes/value_test.cpp | 10 ++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/include/scratchcpp/value_functions.h b/include/scratchcpp/value_functions.h index d4409a0b..1542d2b9 100644 --- a/include/scratchcpp/value_functions.h +++ b/include/scratchcpp/value_functions.h @@ -34,6 +34,8 @@ extern "C" LIBSCRATCHCPP_EXPORT char *value_toCString(const ValueData *v); LIBSCRATCHCPP_EXPORT void value_toUtf16(const ValueData *v, std::u16string *dst); + LIBSCRATCHCPP_EXPORT bool value_doubleIsInt(double v); + LIBSCRATCHCPP_EXPORT char *value_doubleToCString(double v); LIBSCRATCHCPP_EXPORT const char *value_boolToCString(bool v); LIBSCRATCHCPP_EXPORT double value_stringToDouble(const char *s); diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index 710facd5..0da37ff3 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -164,14 +164,9 @@ extern "C" case ValueType::Bool: return true; - case ValueType::Number: { - if (std::isinf(v->numberValue) || std::isnan(v->numberValue)) - return true; + case ValueType::Number: + return value_doubleIsInt(v->numberValue); - double intpart; - std::modf(v->numberValue, &intpart); - return v->numberValue == intpart; - } case ValueType::String: return value_checkString(v->stringValue) == 1; } @@ -280,6 +275,17 @@ extern "C" dst->assign(utf8::utf8to16(s)); } + /*! Returns true if the given number represents a round integer. */ + bool value_doubleIsInt(double v) + { + if (std::isinf(v) || std::isnan(v)) + return true; + + double intpart; + std::modf(v, &intpart); + return v == intpart; + } + /*! * Converts the given number to string. * \note It is the caller's responsibility to free allocated memory. diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index 04cce470..f59f3078 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -2795,6 +2795,16 @@ TEST(ValueTest, ComparisonOperators) } } +TEST(ValueTest, DoubleIsInt) +{ + ASSERT_TRUE(value_doubleIsInt(0.0)); + ASSERT_TRUE(value_doubleIsInt(15.0)); + ASSERT_TRUE(value_doubleIsInt(-468.0)); + ASSERT_FALSE(value_doubleIsInt(0.1)); + ASSERT_FALSE(value_doubleIsInt(1.2)); + ASSERT_FALSE(value_doubleIsInt(-12.5852)); +} + TEST(ValueTest, DoubleToCString) { char *ret; From 1a68af02353587bd3480cc334c02cb87aa844acb Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:13:49 +0100 Subject: [PATCH 07/25] LLVMCodeBuilder: Add createRandom() method --- src/dev/engine/internal/icodebuilder.h | 2 + src/dev/engine/internal/llvm/CMakeLists.txt | 2 + .../engine/internal/llvm/llvmcodebuilder.cpp | 53 ++++++++ .../engine/internal/llvm/llvmcodebuilder.h | 5 + .../engine/internal/llvm/llvmfunctions.cpp | 38 ++++++ src/dev/engine/internal/llvm/llvmfunctions.h | 11 ++ .../engine/internal/llvm/llvminstruction.h | 1 + test/dev/llvm/llvmcodebuilder_test.cpp | 125 +++++++++++++++++- test/mocks/codebuildermock.h | 2 + 9 files changed, 234 insertions(+), 5 deletions(-) create mode 100644 src/dev/engine/internal/llvm/llvmfunctions.cpp create mode 100644 src/dev/engine/internal/llvm/llvmfunctions.h diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h index c92c3222..d65a24f0 100644 --- a/src/dev/engine/internal/icodebuilder.h +++ b/src/dev/engine/internal/icodebuilder.h @@ -36,6 +36,8 @@ class ICodeBuilder virtual CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) = 0; virtual CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) = 0; + virtual CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) = 0; virtual CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) = 0; virtual CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) = 0; diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt index 4ef97a05..7bd34c4d 100644 --- a/src/dev/engine/internal/llvm/CMakeLists.txt +++ b/src/dev/engine/internal/llvm/CMakeLists.txt @@ -15,6 +15,8 @@ target_sources(scratchcpp llvmprocedure.h llvmtypes.cpp llvmtypes.h + llvmfunctions.cpp + llvmfunctions.h llvmexecutablecode.cpp llvmexecutablecode.h llvmexecutioncontext.cpp diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 959871ee..c348b387 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -191,6 +191,38 @@ std::shared_ptr LLVMCodeBuilder::finalize() break; } + case LLVMInstruction::Type::Random: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + LLVMRegister *reg1 = arg1.second; + LLVMRegister *reg2 = arg2.second; + + if (reg1->type() == Compiler::StaticType::Bool && reg2->type() == Compiler::StaticType::Bool) { + llvm::Value *bool1 = castValue(arg1.second, Compiler::StaticType::Bool); + llvm::Value *bool2 = castValue(arg2.second, Compiler::StaticType::Bool); + step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_bool(), { bool1, bool2 }); + } else { + llvm::Constant *inf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false); + llvm::Value *num1 = removeNaN(castValue(arg1.second, Compiler::StaticType::Number)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, Compiler::StaticType::Number)); + llvm::Value *sum = m_builder.CreateFAdd(num1, num2); + llvm::Value *sumDiv = m_builder.CreateFDiv(sum, inf); + llvm::Value *isInfOrNaN = isNaN(sumDiv); + + // NOTE: The random function will be called even in edge cases where it isn't needed, but they're rare, so it shouldn't be an issue + if (reg1->type() == Compiler::StaticType::Number && reg2->type() == Compiler::StaticType::Number) + step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random_double(), { num1, num2 })); + else { + llvm::Value *value1 = createValue(reg1); + llvm::Value *value2 = createValue(reg2); + step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random(), { value1, value2 })); + } + } + + break; + } + case LLVMInstruction::Type::CmpEQ: { assert(step.args.size() == 2); const auto &arg1 = step.args[0].second; @@ -1089,6 +1121,11 @@ CompilerValue *LLVMCodeBuilder::createDiv(CompilerValue *operand1, CompilerValue return createOp(LLVMInstruction::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 }); } +CompilerValue *LLVMCodeBuilder::createRandom(CompilerValue *from, CompilerValue *to) +{ + return createOp(LLVMInstruction::Type::Random, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { from, to }); +} + CompilerValue *LLVMCodeBuilder::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) { return createOp(LLVMInstruction::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 }); @@ -2334,6 +2371,22 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string() return resolveFunction("list_to_string", llvm::FunctionType::get(pointerType, { pointerType }, false)); } +llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random() +{ + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { valuePtr, valuePtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double() +{ + return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool() +{ + return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false)); +} + llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp() { llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index afdf12de..62e4aadf 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -43,6 +43,8 @@ class LLVMCodeBuilder : public ICodeBuilder CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) override; CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) override; + CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) override; + CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) override; CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) override; CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) override; @@ -166,6 +168,9 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::FunctionCallee resolve_list_size_ptr(); llvm::FunctionCallee resolve_list_alloc_size_ptr(); llvm::FunctionCallee resolve_list_to_string(); + llvm::FunctionCallee resolve_llvm_random(); + llvm::FunctionCallee resolve_llvm_random_double(); + llvm::FunctionCallee resolve_llvm_random_bool(); llvm::FunctionCallee resolve_strcasecmp(); Target *m_target = nullptr; diff --git a/src/dev/engine/internal/llvm/llvmfunctions.cpp b/src/dev/engine/internal/llvm/llvmfunctions.cpp new file mode 100644 index 00000000..8664ceb2 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmfunctions.cpp @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "llvmfunctions.h" +#include "../../../../engine/internal/randomgenerator.h" + +namespace libscratchcpp +{ + +extern "C" +{ + double llvm_random(ValueData *from, ValueData *to) + { + if (!llvm_rng) + llvm_rng = RandomGenerator::instance().get(); + + return value_isInt(from) && value_isInt(to) ? llvm_rng->randint(value_toLong(from), value_toLong(to)) : llvm_rng->randintDouble(value_toDouble(from), value_toDouble(to)); + } + + double llvm_random_double(double from, double to) + { + if (!llvm_rng) + llvm_rng = RandomGenerator::instance().get(); + + return value_doubleIsInt(from) && value_doubleIsInt(to) ? llvm_rng->randint(from, to) : llvm_rng->randintDouble(from, to); + } + + double llvm_random_bool(bool from, bool to) + { + if (!llvm_rng) + llvm_rng = RandomGenerator::instance().get(); + + return llvm_rng->randint(from, to); + } +} + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmfunctions.h b/src/dev/engine/internal/llvm/llvmfunctions.h new file mode 100644 index 00000000..cf078872 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmfunctions.h @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 +#pragma once + +namespace libscratchcpp +{ + +class IRandomGenerator; + +IRandomGenerator *llvm_rng = nullptr; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h index 9b81b563..f3d2fa7b 100644 --- a/src/dev/engine/internal/llvm/llvminstruction.h +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -18,6 +18,7 @@ struct LLVMInstruction Sub, Mul, Div, + Random, CmpEQ, CmpGT, CmpLT, diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index 6fd62ac1..a67dd965 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -7,9 +7,11 @@ #include #include #include +#include #include #include #include +#include #include "testfunctions.h" @@ -27,6 +29,7 @@ class LLVMCodeBuilderTest : public testing::Test Sub, Mul, Div, + Random, CmpEQ, CmpGT, CmpLT, @@ -49,7 +52,6 @@ class LLVMCodeBuilderTest : public testing::Test Log10, Exp, Exp10 - }; void SetUp() override @@ -93,6 +95,9 @@ class LLVMCodeBuilderTest : public testing::Test case OpType::Div: return m_builder->createDiv(arg1, arg2); + case OpType::Random: + return m_builder->createRandom(arg1, arg2); + case OpType::CmpEQ: return m_builder->createCmpEQ(arg1, arg2); @@ -189,6 +194,15 @@ class LLVMCodeBuilderTest : public testing::Test case OpType::Div: return v1 / v2; + case OpType::Random: { + const double sum = v1.toDouble() + v2.toDouble(); + + if (std::isnan(sum) || std::isinf(sum)) + return sum; + + return v1.isInt() && v2.isInt() ? m_rng.randint(v1.toLong(), v2.toLong()) : m_rng.randintDouble(v1.toDouble(), v2.toDouble()); + } + case OpType::CmpEQ: return v1 == v2; @@ -225,7 +239,7 @@ class LLVMCodeBuilderTest : public testing::Test } } - void runOpTest(OpType type, const Value &v1, const Value &v2) + void runOpTestCommon(OpType type, const Value &v1, const Value &v2) { createBuilder(true); @@ -241,9 +255,6 @@ class LLVMCodeBuilderTest : public testing::Test ret = addOp(type, arg1, arg2); m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret }); - std::string str = doOp(type, v1, v2).toString() + '\n'; - std::string expected = str + str; - auto code = m_builder->finalize(); Script script(&m_target, nullptr, nullptr); script.setCode(code); @@ -252,9 +263,28 @@ class LLVMCodeBuilderTest : public testing::Test testing::internal::CaptureStdout(); code->run(ctx.get()); + } + + void checkOpTest(const Value &v1, const Value &v2, const std::string &expected) + { const std::string quotes1 = v1.isString() ? "\"" : ""; const std::string quotes2 = v2.isString() ? "\"" : ""; ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; + } + + void runOpTest(OpType type, const Value &v1, const Value &v2, const Value &expected) + { + std::string str = expected.toString(); + runOpTestCommon(type, v1, v2); + checkOpTest(v1, v2, str + '\n' + str + '\n'); + }; + + void runOpTest(OpType type, const Value &v1, const Value &v2) + { + runOpTestCommon(type, v1, v2); + std::string str = doOp(type, v1, v2).toString() + '\n'; + std::string expected = str + str; + checkOpTest(v1, v2, expected); }; void runOpTest(OpType type, const Value &v) @@ -317,6 +347,7 @@ class LLVMCodeBuilderTest : public testing::Test std::unique_ptr m_builder; TargetMock m_target; // NOTE: isStage() is used for call expectations + RandomGeneratorMock m_rng; }; TEST_F(LLVMCodeBuilderTest, FunctionCalls) @@ -605,6 +636,90 @@ TEST_F(LLVMCodeBuilderTest, Divide) runOpTest(OpType::Div, 0, 0); } +TEST_F(LLVMCodeBuilderTest, Random) +{ + llvm_rng = &m_rng; + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18)); + runOpTest(OpType::Random, -45, 12); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5)); + runOpTest(OpType::Random, -45.0, 12.0); + + EXPECT_CALL(m_rng, randintDouble(12, 6.05)).Times(3).WillRepeatedly(Return(3.486789)); + runOpTest(OpType::Random, 12, 6.05); + + EXPECT_CALL(m_rng, randintDouble(-78.686, -45)).Times(3).WillRepeatedly(Return(-59.468873)); + runOpTest(OpType::Random, -78.686, -45); + + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(-28.648764)); + runOpTest(OpType::Random, 6.05, -78.686); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, "-45", "12"); + + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(5.2)); + runOpTest(OpType::Random, "-45.0", "12"); + + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(-15.5787)); + runOpTest(OpType::Random, "-45", "12.0"); + + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(2.587964)); + runOpTest(OpType::Random, "-45.0", "12.0"); + + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764)); + runOpTest(OpType::Random, "6.05", "-78.686"); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, "-45", 12); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, -45, "12"); + + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(5.2)); + runOpTest(OpType::Random, "-45.0", 12); + + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(-15.5787)); + runOpTest(OpType::Random, -45, "12.0"); + + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764)); + runOpTest(OpType::Random, 6.05, "-78.686"); + + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764)); + runOpTest(OpType::Random, "6.05", -78.686); + + EXPECT_CALL(m_rng, randint(0, 1)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::Random, false, true); + + EXPECT_CALL(m_rng, randint(1, 5)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::Random, true, 5); + + EXPECT_CALL(m_rng, randint(8, 0)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::Random, 8, false); + + const double inf = std::numeric_limits::infinity(); + const double nan = std::numeric_limits::quiet_NaN(); + EXPECT_CALL(m_rng, randint).WillRepeatedly(Return(0)); + EXPECT_CALL(m_rng, randintDouble).WillRepeatedly(Return(0)); + + runOpTest(OpType::Random, inf, 2, inf); + runOpTest(OpType::Random, -8, inf, inf); + runOpTest(OpType::Random, -inf, -2, -inf); + runOpTest(OpType::Random, 8, -inf, -inf); + + runOpTest(OpType::Random, inf, 2.5, inf); + runOpTest(OpType::Random, -8.09, inf, inf); + runOpTest(OpType::Random, -inf, -2.5, -inf); + runOpTest(OpType::Random, 8.09, -inf, -inf); + + runOpTest(OpType::Random, inf, inf, inf); + runOpTest(OpType::Random, -inf, -inf, -inf); + runOpTest(OpType::Random, inf, -inf, nan); + runOpTest(OpType::Random, -inf, inf, nan); + + llvm_rng = nullptr; +} + TEST_F(LLVMCodeBuilderTest, EqualComparison) { runOpTest(OpType::CmpEQ, 10, 10); diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h index 478252d6..bd381144 100644 --- a/test/mocks/codebuildermock.h +++ b/test/mocks/codebuildermock.h @@ -26,6 +26,8 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(CompilerValue *, createMul, (CompilerValue *, CompilerValue *), (override)); MOCK_METHOD(CompilerValue *, createDiv, (CompilerValue *, CompilerValue *), (override)); + MOCK_METHOD(CompilerValue *, createRandom, (CompilerValue *, CompilerValue *), (override)); + MOCK_METHOD(CompilerValue *, createCmpEQ, (CompilerValue *, CompilerValue *), (override)); MOCK_METHOD(CompilerValue *, createCmpGT, (CompilerValue *, CompilerValue *), (override)); MOCK_METHOD(CompilerValue *, createCmpLT, (CompilerValue *, CompilerValue *), (override)); From 5736a0fe2f7094d3d4566939031eec9cc1da66b6 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:55:49 +0100 Subject: [PATCH 08/25] Move IRandomGenerator to public API --- CMakeLists.txt | 1 + include/scratchcpp/irandomgenerator.h | 26 ++++++++++++++++++++++++++ src/engine/CMakeLists.txt | 1 - src/engine/internal/irandomgenerator.h | 18 ------------------ src/engine/internal/randomgenerator.h | 3 +-- test/mocks/randomgeneratormock.h | 2 +- 6 files changed, 29 insertions(+), 22 deletions(-) create mode 100644 include/scratchcpp/irandomgenerator.h delete mode 100644 src/engine/internal/irandomgenerator.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c08d26ae..1aaebf77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ target_sources(scratchcpp include/scratchcpp/textbubble.h include/scratchcpp/itimer.h include/scratchcpp/istacktimer.h + include/scratchcpp/irandomgenerator.h include/scratchcpp/keyevent.h include/scratchcpp/rect.h include/scratchcpp/igraphicseffect.h diff --git a/include/scratchcpp/irandomgenerator.h b/include/scratchcpp/irandomgenerator.h new file mode 100644 index 00000000..1fa1d79c --- /dev/null +++ b/include/scratchcpp/irandomgenerator.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "global.h" + +namespace libscratchcpp +{ + +/*! \brief The IRandomGenerator interface represents a random number generator that can be received e. g. from an ExecutionContext. */ +class LIBSCRATCHCPP_EXPORT IRandomGenerator +{ + public: + virtual ~IRandomGenerator() { } + + /*! Returns a random integer in the given range (inclusive). */ + virtual long randint(long start, long end) const = 0; + + /*! Returns a random double in the given range (inclusive). */ + virtual double randintDouble(double start, double end) const = 0; + + /*! Returns a random integer in the given range (inclusive) except the given integer. */ + virtual long randintExcept(long start, long end, long except) const = 0; +}; + +} // namespace libscratchcpp diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 506489de..d1b47737 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -20,7 +20,6 @@ target_sources(scratchcpp internal/stacktimer.h internal/randomgenerator.h internal/randomgenerator.cpp - internal/irandomgenerator.h ) if(NOT LIBSCRATCHCPP_USE_LLVM) diff --git a/src/engine/internal/irandomgenerator.h b/src/engine/internal/irandomgenerator.h deleted file mode 100644 index 2228c933..00000000 --- a/src/engine/internal/irandomgenerator.h +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -namespace libscratchcpp -{ - -class IRandomGenerator -{ - public: - virtual ~IRandomGenerator() { } - - virtual long randint(long start, long end) const = 0; - virtual double randintDouble(double start, double end) const = 0; - virtual long randintExcept(long start, long end, long except) const = 0; -}; - -} // namespace libscratchcpp diff --git a/src/engine/internal/randomgenerator.h b/src/engine/internal/randomgenerator.h index 1358de37..69d7cabd 100644 --- a/src/engine/internal/randomgenerator.h +++ b/src/engine/internal/randomgenerator.h @@ -2,11 +2,10 @@ #pragma once +#include #include #include -#include "irandomgenerator.h" - namespace libscratchcpp { diff --git a/test/mocks/randomgeneratormock.h b/test/mocks/randomgeneratormock.h index 6e07f881..eea08335 100644 --- a/test/mocks/randomgeneratormock.h +++ b/test/mocks/randomgeneratormock.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include using namespace libscratchcpp; From f782936efd9922a99cdb4ee528f1b7d0acde3c89 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:57:08 +0100 Subject: [PATCH 09/25] ExecutionContext: Add random number generator --- include/scratchcpp/dev/executioncontext.h | 4 ++++ src/dev/engine/executioncontext.cpp | 12 ++++++++++++ src/dev/engine/executioncontext_p.cpp | 4 +++- src/dev/engine/executioncontext_p.h | 2 ++ test/dev/executioncontext/executioncontext_test.cpp | 11 +++++++++++ 5 files changed, 32 insertions(+), 1 deletion(-) diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h index 970041c0..69d11945 100644 --- a/include/scratchcpp/dev/executioncontext.h +++ b/include/scratchcpp/dev/executioncontext.h @@ -12,6 +12,7 @@ class Thread; class IEngine; class Promise; class IStackTimer; +class IRandomGenerator; class ExecutionContextPrivate; /*! \brief The ExecutionContext represents the execution context of a target (can be a clone) with variables, lists, etc. */ @@ -31,6 +32,9 @@ class LIBSCRATCHCPP_EXPORT ExecutionContext IStackTimer *stackTimer() const; void setStackTimer(IStackTimer *newStackTimer); + IRandomGenerator *rng() const; + void setRng(IRandomGenerator *newRng); + private: spimpl::unique_impl_ptr impl; }; diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp index 5c72cd54..fb0d9327 100644 --- a/src/dev/engine/executioncontext.cpp +++ b/src/dev/engine/executioncontext.cpp @@ -48,3 +48,15 @@ void ExecutionContext::setStackTimer(IStackTimer *newStackTimer) { impl->stackTimer = newStackTimer; } + +/*! Returns the random number generator of this context. */ +IRandomGenerator *ExecutionContext::rng() const +{ + return impl->rng; +} + +/*! Sets a custom random number generator. */ +void ExecutionContext::setRng(IRandomGenerator *newRng) +{ + impl->rng = newRng; +} diff --git a/src/dev/engine/executioncontext_p.cpp b/src/dev/engine/executioncontext_p.cpp index 03970aa7..94dd1072 100644 --- a/src/dev/engine/executioncontext_p.cpp +++ b/src/dev/engine/executioncontext_p.cpp @@ -1,12 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 #include "executioncontext_p.h" +#include "../../engine/internal/randomgenerator.h" using namespace libscratchcpp; ExecutionContextPrivate::ExecutionContextPrivate(Thread *thread) : thread(thread), defaultStackTimer(std::make_unique()), - stackTimer(defaultStackTimer.get()) + stackTimer(defaultStackTimer.get()), + rng(RandomGenerator::instance().get()) { } diff --git a/src/dev/engine/executioncontext_p.h b/src/dev/engine/executioncontext_p.h index c556839d..367452aa 100644 --- a/src/dev/engine/executioncontext_p.h +++ b/src/dev/engine/executioncontext_p.h @@ -11,6 +11,7 @@ namespace libscratchcpp class Thread; class Promise; +class IRandomGenerator; struct ExecutionContextPrivate { @@ -20,6 +21,7 @@ struct ExecutionContextPrivate std::shared_ptr promise; std::unique_ptr defaultStackTimer; IStackTimer *stackTimer = nullptr; + IRandomGenerator *rng = nullptr; }; } // namespace libscratchcpp diff --git a/test/dev/executioncontext/executioncontext_test.cpp b/test/dev/executioncontext/executioncontext_test.cpp index 48504cd1..d6945813 100644 --- a/test/dev/executioncontext/executioncontext_test.cpp +++ b/test/dev/executioncontext/executioncontext_test.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "../../common.h" @@ -40,3 +41,13 @@ TEST(ExecutionContextTest, StackTimer) ctx.setStackTimer(&timer); ASSERT_EQ(ctx.stackTimer(), &timer); } + +TEST(ExecutionContextTest, Rng) +{ + ExecutionContext ctx(nullptr); + ASSERT_TRUE(ctx.rng()); + + RandomGeneratorMock rng; + ctx.setRng(&rng); + ASSERT_EQ(ctx.rng(), &rng); +} From 0ef6a2b4f8a41584d69590bdff5f32ad4314c734 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:57:42 +0100 Subject: [PATCH 10/25] LLVMCodeBuilder: Use random number generator from execution context --- src/dev/engine/internal/llvm/CMakeLists.txt | 1 - .../engine/internal/llvm/llvmcodebuilder.cpp | 15 ++++++----- .../engine/internal/llvm/llvmfunctions.cpp | 26 ++++++------------- src/dev/engine/internal/llvm/llvmfunctions.h | 11 -------- test/dev/llvm/llvmcodebuilder_test.cpp | 7 ++--- 5 files changed, 19 insertions(+), 41 deletions(-) delete mode 100644 src/dev/engine/internal/llvm/llvmfunctions.h diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt index 7bd34c4d..315be55a 100644 --- a/src/dev/engine/internal/llvm/CMakeLists.txt +++ b/src/dev/engine/internal/llvm/CMakeLists.txt @@ -16,7 +16,6 @@ target_sources(scratchcpp llvmtypes.cpp llvmtypes.h llvmfunctions.cpp - llvmfunctions.h llvmexecutablecode.cpp llvmexecutablecode.h llvmexecutioncontext.cpp diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index c348b387..74698bf2 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -201,7 +201,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() if (reg1->type() == Compiler::StaticType::Bool && reg2->type() == Compiler::StaticType::Bool) { llvm::Value *bool1 = castValue(arg1.second, Compiler::StaticType::Bool); llvm::Value *bool2 = castValue(arg2.second, Compiler::StaticType::Bool); - step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_bool(), { bool1, bool2 }); + step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_bool(), { executionContextPtr, bool1, bool2 }); } else { llvm::Constant *inf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false); llvm::Value *num1 = removeNaN(castValue(arg1.second, Compiler::StaticType::Number)); @@ -212,11 +212,11 @@ std::shared_ptr LLVMCodeBuilder::finalize() // NOTE: The random function will be called even in edge cases where it isn't needed, but they're rare, so it shouldn't be an issue if (reg1->type() == Compiler::StaticType::Number && reg2->type() == Compiler::StaticType::Number) - step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random_double(), { num1, num2 })); + step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random_double(), { executionContextPtr, num1, num2 })); else { llvm::Value *value1 = createValue(reg1); llvm::Value *value2 = createValue(reg2); - step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random(), { value1, value2 })); + step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random(), { executionContextPtr, value1, value2 })); } } @@ -2373,18 +2373,21 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string() llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random() { + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); llvm::Type *valuePtr = m_valueDataType->getPointerTo(); - return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { valuePtr, valuePtr }, false)); + return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, valuePtr, valuePtr }, false)); } llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double() { - return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false)); + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false)); } llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool() { - return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false)); + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false)); } llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp() diff --git a/src/dev/engine/internal/llvm/llvmfunctions.cpp b/src/dev/engine/internal/llvm/llvmfunctions.cpp index 8664ceb2..d07be391 100644 --- a/src/dev/engine/internal/llvm/llvmfunctions.cpp +++ b/src/dev/engine/internal/llvm/llvmfunctions.cpp @@ -1,37 +1,27 @@ // SPDX-License-Identifier: Apache-2.0 #include - -#include "llvmfunctions.h" -#include "../../../../engine/internal/randomgenerator.h" +#include +#include namespace libscratchcpp { extern "C" { - double llvm_random(ValueData *from, ValueData *to) + double llvm_random(ExecutionContext *ctx, ValueData *from, ValueData *to) { - if (!llvm_rng) - llvm_rng = RandomGenerator::instance().get(); - - return value_isInt(from) && value_isInt(to) ? llvm_rng->randint(value_toLong(from), value_toLong(to)) : llvm_rng->randintDouble(value_toDouble(from), value_toDouble(to)); + return value_isInt(from) && value_isInt(to) ? ctx->rng()->randint(value_toLong(from), value_toLong(to)) : ctx->rng()->randintDouble(value_toDouble(from), value_toDouble(to)); } - double llvm_random_double(double from, double to) + double llvm_random_double(ExecutionContext *ctx, double from, double to) { - if (!llvm_rng) - llvm_rng = RandomGenerator::instance().get(); - - return value_doubleIsInt(from) && value_doubleIsInt(to) ? llvm_rng->randint(from, to) : llvm_rng->randintDouble(from, to); + return value_doubleIsInt(from) && value_doubleIsInt(to) ? ctx->rng()->randint(from, to) : ctx->rng()->randintDouble(from, to); } - double llvm_random_bool(bool from, bool to) + double llvm_random_bool(ExecutionContext *ctx, bool from, bool to) { - if (!llvm_rng) - llvm_rng = RandomGenerator::instance().get(); - - return llvm_rng->randint(from, to); + return ctx->rng()->randint(from, to); } } diff --git a/src/dev/engine/internal/llvm/llvmfunctions.h b/src/dev/engine/internal/llvm/llvmfunctions.h deleted file mode 100644 index cf078872..00000000 --- a/src/dev/engine/internal/llvm/llvmfunctions.h +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -#pragma once - -namespace libscratchcpp -{ - -class IRandomGenerator; - -IRandomGenerator *llvm_rng = nullptr; - -} // namespace libscratchcpp diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index a67dd965..fa631be4 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -7,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -260,6 +260,7 @@ class LLVMCodeBuilderTest : public testing::Test script.setCode(code); Thread thread(&m_target, nullptr, &script); auto ctx = code->createExecutionContext(&thread); + ctx->setRng(&m_rng); testing::internal::CaptureStdout(); code->run(ctx.get()); @@ -638,8 +639,6 @@ TEST_F(LLVMCodeBuilderTest, Divide) TEST_F(LLVMCodeBuilderTest, Random) { - llvm_rng = &m_rng; - EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18)); runOpTest(OpType::Random, -45, 12); @@ -716,8 +715,6 @@ TEST_F(LLVMCodeBuilderTest, Random) runOpTest(OpType::Random, -inf, -inf, -inf); runOpTest(OpType::Random, inf, -inf, nan); runOpTest(OpType::Random, -inf, inf, nan); - - llvm_rng = nullptr; } TEST_F(LLVMCodeBuilderTest, EqualComparison) From a83dae74f9747f9039d870d9ea122bd7f7c903bf Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:49:37 +0100 Subject: [PATCH 11/25] Compiler: Add createRandom() method --- include/scratchcpp/dev/compiler.h | 2 ++ src/dev/engine/compiler.cpp | 6 ++++++ test/dev/compiler/compiler_test.cpp | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h index b7f5cc73..eb70df4d 100644 --- a/include/scratchcpp/dev/compiler.h +++ b/include/scratchcpp/dev/compiler.h @@ -65,6 +65,8 @@ class LIBSCRATCHCPP_EXPORT Compiler CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2); CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2); + CompilerValue *createRandom(CompilerValue *from, CompilerValue *to); + CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2); CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2); CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2); diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp index 0c633c72..807fa11f 100644 --- a/src/dev/engine/compiler.cpp +++ b/src/dev/engine/compiler.cpp @@ -193,6 +193,12 @@ CompilerValue *Compiler::createDiv(CompilerValue *operand1, CompilerValue *opera return impl->builder->createDiv(operand1, operand2); } +/*! Creates a random instruction (Scratch behavior). */ +CompilerValue *Compiler::createRandom(CompilerValue *from, CompilerValue *to) +{ + return impl->builder->createRandom(from, to); +} + /*! Creates an equality comparison instruction. */ CompilerValue *Compiler::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) { diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp index cacd80b5..c2abc8d2 100644 --- a/test/dev/compiler/compiler_test.cpp +++ b/test/dev/compiler/compiler_test.cpp @@ -466,6 +466,24 @@ TEST_F(CompilerTest, CreateDiv) compile(compiler, block); } +TEST_F(CompilerTest, CreateRandom) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createRandom(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createRandom(&arg1, &arg2), &ret); + + return nullptr; + }); + + compile(compiler, block); +} + TEST_F(CompilerTest, CreateCmpEQ) { Compiler compiler(&m_engine, &m_target); From b678de0a86562f64a3f1bb3a4aa0e6abc83f4fbd Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:50:09 +0100 Subject: [PATCH 12/25] Implement operator_random --- src/dev/blocks/operatorblocks.cpp | 9 ++++ src/dev/blocks/operatorblocks.h | 3 ++ test/dev/blocks/CMakeLists.txt | 1 + test/dev/blocks/operator_blocks_test.cpp | 61 ++++++++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index f98a924b..d7523475 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "operatorblocks.h" @@ -23,6 +24,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_subtract", &compileSubtract); engine->addCompileFunction(this, "operator_multiply", &compileMultiply); engine->addCompileFunction(this, "operator_divide", &compileDivide); + engine->addCompileFunction(this, "operator_random", &compileRandom); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -44,3 +46,10 @@ CompilerValue *OperatorBlocks::compileDivide(Compiler *compiler) { return compiler->createDiv(compiler->addInput("NUM1"), compiler->addInput("NUM2")); } + +CompilerValue *OperatorBlocks::compileRandom(Compiler *compiler) +{ + auto from = compiler->addInput("FROM"); + auto to = compiler->addInput("TO"); + return compiler->createRandom(from, to); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 89b7a1d6..5f1c9aba 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -7,6 +7,8 @@ namespace libscratchcpp { +class IRandomGenerator; + class OperatorBlocks : public IExtension { public: @@ -20,6 +22,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileSubtract(Compiler *compiler); static CompilerValue *compileMultiply(Compiler *compiler); static CompilerValue *compileDivide(Compiler *compiler); + static CompilerValue *compileRandom(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/CMakeLists.txt b/test/dev/blocks/CMakeLists.txt index 3fc113f7..c36e43cf 100644 --- a/test/dev/blocks/CMakeLists.txt +++ b/test/dev/blocks/CMakeLists.txt @@ -132,6 +132,7 @@ if (LIBSCRATCHCPP_ENABLE_OPERATOR_BLOCKS) GTest::gmock_main scratchcpp scratchcpp_mocks + block_test_deps ) gtest_discover_tests(operator_blocks_test) diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 13282adb..f77041d8 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -2,15 +2,25 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include +#include #include "../common.h" #include "dev/blocks/operatorblocks.h" +#include "util.h" using namespace libscratchcpp; using namespace libscratchcpp::test; +using ::testing::Return; + class OperatorBlocksTest : public testing::Test { public: @@ -19,12 +29,14 @@ class OperatorBlocksTest : public testing::Test m_extension = std::make_unique(); m_engine = m_project.engine().get(); m_extension->registerBlocks(m_engine); + registerBlocks(m_engine, m_extension.get()); } std::unique_ptr m_extension; Project m_project; IEngine *m_engine = nullptr; EngineMock m_engineMock; + RandomGeneratorMock m_rng; }; TEST_F(OperatorBlocksTest, Add) @@ -102,3 +114,52 @@ TEST_F(OperatorBlocksTest, Divide) ASSERT_EQ(valueList->size(), 1); ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 2.28); } + +TEST_F(OperatorBlocksTest, Random) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addRandomTest = [&builder](const Value &from, const Value &to) { + auto block = std::make_shared("", "operator_random"); + auto input = std::make_shared("FROM", Input::Type::Shadow); + input->setPrimaryValue(from); + block->addInput(input); + input = std::make_shared("TO", Input::Type::Shadow); + input->setPrimaryValue(to); + block->addInput(input); + + builder.addBlock("test_print"); + builder.addObscuredInput("STRING", block); + return builder.currentBlock(); + }; + + auto block = addRandomTest(-45, 12); + addRandomTest(12, 6.05); + addRandomTest(-78.686, -45); + addRandomTest(6.05, -78.686); + + builder.build(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + ctx->setRng(&m_rng); + + static const std::string expected = + "-18\n" + "3.486789\n" + "-59.468873\n" + "-28.648764\n"; + + EXPECT_CALL(m_rng, randint(-45, 12)).WillOnce(Return(-18)); + EXPECT_CALL(m_rng, randintDouble(12, 6.05)).WillOnce(Return(3.486789)); + EXPECT_CALL(m_rng, randintDouble(-78.686, -45)).WillOnce(Return(-59.468873)); + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).WillOnce(Return(-28.648764)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} From 3a39a57ac47680885c34e1636fd294229f9d88ce Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:02:27 +0100 Subject: [PATCH 13/25] Implement operator_lt --- src/dev/blocks/operatorblocks.cpp | 6 +++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 31 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index d7523475..c9f8937b 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -25,6 +25,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_multiply", &compileMultiply); engine->addCompileFunction(this, "operator_divide", &compileDivide); engine->addCompileFunction(this, "operator_random", &compileRandom); + engine->addCompileFunction(this, "operator_lt", &compileLt); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -53,3 +54,8 @@ CompilerValue *OperatorBlocks::compileRandom(Compiler *compiler) auto to = compiler->addInput("TO"); return compiler->createRandom(from, to); } + +CompilerValue *OperatorBlocks::compileLt(Compiler *compiler) +{ + return compiler->createCmpLT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 5f1c9aba..5eccac85 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -23,6 +23,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileMultiply(Compiler *compiler); static CompilerValue *compileDivide(Compiler *compiler); static CompilerValue *compileRandom(Compiler *compiler); + static CompilerValue *compileLt(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index f77041d8..f89f04b8 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -163,3 +163,34 @@ TEST_F(OperatorBlocksTest, Random) code->run(ctx.get()); ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); } + +TEST_F(OperatorBlocksTest, Lt) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_lt"); + builder.addValueInput("OPERAND1", 5.4645); + builder.addValueInput("OPERAND2", 12.486); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_lt"); + builder.addValueInput("OPERAND1", 153.25); + builder.addValueInput("OPERAND2", 96.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_lt"); + builder.addValueInput("OPERAND1", 2.8465); + builder.addValueInput("OPERAND2", 2.8465); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), true); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), false); +} From c040d5ef70835b679255e52868a1be38385e2c3e Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:07:01 +0100 Subject: [PATCH 14/25] Implement operator_equals --- src/dev/blocks/operatorblocks.cpp | 6 +++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 31 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index c9f8937b..1ed1f0ff 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -26,6 +26,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_divide", &compileDivide); engine->addCompileFunction(this, "operator_random", &compileRandom); engine->addCompileFunction(this, "operator_lt", &compileLt); + engine->addCompileFunction(this, "operator_equals", &compileEquals); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -59,3 +60,8 @@ CompilerValue *OperatorBlocks::compileLt(Compiler *compiler) { return compiler->createCmpLT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); } + +CompilerValue *OperatorBlocks::compileEquals(Compiler *compiler) +{ + return compiler->createCmpEQ(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 5eccac85..e1c46064 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -24,6 +24,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileDivide(Compiler *compiler); static CompilerValue *compileRandom(Compiler *compiler); static CompilerValue *compileLt(Compiler *compiler); + static CompilerValue *compileEquals(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index f89f04b8..d7812863 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -194,3 +194,34 @@ TEST_F(OperatorBlocksTest, Lt) ASSERT_EQ(Value(values[1]), false); ASSERT_EQ(Value(values[2]), false); } + +TEST_F(OperatorBlocksTest, Equals) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_equals"); + builder.addValueInput("OPERAND1", 5.4645); + builder.addValueInput("OPERAND2", 12.486); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_equals"); + builder.addValueInput("OPERAND1", 153.25); + builder.addValueInput("OPERAND2", 96.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_equals"); + builder.addValueInput("OPERAND1", 2.8465); + builder.addValueInput("OPERAND2", 2.8465); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), true); +} From d3ef6a1144d0b5b95631a99f89d5361eb2c38d8c Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:08:02 +0100 Subject: [PATCH 15/25] Implement operator_gt --- src/dev/blocks/operatorblocks.cpp | 6 +++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 31 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 1ed1f0ff..9a180e1c 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -27,6 +27,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_random", &compileRandom); engine->addCompileFunction(this, "operator_lt", &compileLt); engine->addCompileFunction(this, "operator_equals", &compileEquals); + engine->addCompileFunction(this, "operator_gt", &compileGt); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -65,3 +66,8 @@ CompilerValue *OperatorBlocks::compileEquals(Compiler *compiler) { return compiler->createCmpEQ(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); } + +CompilerValue *OperatorBlocks::compileGt(Compiler *compiler) +{ + return compiler->createCmpGT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index e1c46064..21093bdd 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -25,6 +25,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileRandom(Compiler *compiler); static CompilerValue *compileLt(Compiler *compiler); static CompilerValue *compileEquals(Compiler *compiler); + static CompilerValue *compileGt(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index d7812863..aa1ba51c 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -225,3 +225,34 @@ TEST_F(OperatorBlocksTest, Equals) ASSERT_EQ(Value(values[1]), false); ASSERT_EQ(Value(values[2]), true); } + +TEST_F(OperatorBlocksTest, Gt) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_gt"); + builder.addValueInput("OPERAND1", 5.4645); + builder.addValueInput("OPERAND2", 12.486); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_gt"); + builder.addValueInput("OPERAND1", 153.25); + builder.addValueInput("OPERAND2", 96.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_gt"); + builder.addValueInput("OPERAND1", 2.8465); + builder.addValueInput("OPERAND2", 2.8465); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), true); + ASSERT_EQ(Value(values[2]), false); +} From 1ce8e6fb6a1661eff9d724e91bdf10544be5c197 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:14:30 +0100 Subject: [PATCH 16/25] Implement operator_and --- src/dev/blocks/operatorblocks.cpp | 6 ++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 37 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 9a180e1c..7198eec3 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -28,6 +28,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_lt", &compileLt); engine->addCompileFunction(this, "operator_equals", &compileEquals); engine->addCompileFunction(this, "operator_gt", &compileGt); + engine->addCompileFunction(this, "operator_and", &compileAnd); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -71,3 +72,8 @@ CompilerValue *OperatorBlocks::compileGt(Compiler *compiler) { return compiler->createCmpGT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); } + +CompilerValue *OperatorBlocks::compileAnd(Compiler *compiler) +{ + return compiler->createAnd(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 21093bdd..4c8bcc91 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -26,6 +26,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileLt(Compiler *compiler); static CompilerValue *compileEquals(Compiler *compiler); static CompilerValue *compileGt(Compiler *compiler); + static CompilerValue *compileAnd(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index aa1ba51c..d60a81ea 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -256,3 +256,40 @@ TEST_F(OperatorBlocksTest, Gt) ASSERT_EQ(Value(values[1]), true); ASSERT_EQ(Value(values[2]), false); } + +TEST_F(OperatorBlocksTest, And) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 4); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), false); + ASSERT_EQ(Value(values[3]), true); +} From 6e1db6e93d3c51a30b7cf230408015f845207993 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:16:53 +0100 Subject: [PATCH 17/25] Implement operator_or --- src/dev/blocks/operatorblocks.cpp | 6 ++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 37 ++++++++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 7198eec3..7a03b9f3 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -29,6 +29,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_equals", &compileEquals); engine->addCompileFunction(this, "operator_gt", &compileGt); engine->addCompileFunction(this, "operator_and", &compileAnd); + engine->addCompileFunction(this, "operator_or", &compileOr); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -77,3 +78,8 @@ CompilerValue *OperatorBlocks::compileAnd(Compiler *compiler) { return compiler->createAnd(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); } + +CompilerValue *OperatorBlocks::compileOr(Compiler *compiler) +{ + return compiler->createOr(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 4c8bcc91..28af276e 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -27,6 +27,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileEquals(Compiler *compiler); static CompilerValue *compileGt(Compiler *compiler); static CompilerValue *compileAnd(Compiler *compiler); + static CompilerValue *compileOr(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index d60a81ea..80bdfa8f 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -293,3 +293,40 @@ TEST_F(OperatorBlocksTest, And) ASSERT_EQ(Value(values[2]), false); ASSERT_EQ(Value(values[3]), true); } + +TEST_F(OperatorBlocksTest, Or) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 4); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), true); + ASSERT_EQ(Value(values[2]), true); + ASSERT_EQ(Value(values[3]), true); +} From 4ee628ad23b614e94b2791a4cbfeec9c91e856bb Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:22:04 +0100 Subject: [PATCH 18/25] Implement operator_not --- src/dev/blocks/operatorblocks.cpp | 6 ++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 7a03b9f3..ebf4e1f8 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -30,6 +30,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_gt", &compileGt); engine->addCompileFunction(this, "operator_and", &compileAnd); engine->addCompileFunction(this, "operator_or", &compileOr); + engine->addCompileFunction(this, "operator_not", &compileNot); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -83,3 +84,8 @@ CompilerValue *OperatorBlocks::compileOr(Compiler *compiler) { return compiler->createOr(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); } + +CompilerValue *OperatorBlocks::compileNot(Compiler *compiler) +{ + return compiler->createNot(compiler->addInput("OPERAND")); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 28af276e..8570225c 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -28,6 +28,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileGt(Compiler *compiler); static CompilerValue *compileAnd(Compiler *compiler); static CompilerValue *compileOr(Compiler *compiler); + static CompilerValue *compileNot(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 80bdfa8f..2993177e 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -330,3 +330,26 @@ TEST_F(OperatorBlocksTest, Or) ASSERT_EQ(Value(values[2]), true); ASSERT_EQ(Value(values[3]), true); } + +TEST_F(OperatorBlocksTest, Not) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_not"); + builder.addValueInput("OPERAND", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_not"); + builder.addValueInput("OPERAND", true); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(Value(values[0]), true); + ASSERT_EQ(Value(values[1]), false); +} From f66699f2d69270cb8da2749875d772e66a8e1bc1 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 16:40:43 +0100 Subject: [PATCH 19/25] Implement operator_join --- src/dev/blocks/operatorblocks.cpp | 25 ++++++++++++++++++++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 25 ++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index ebf4e1f8..45bb0f75 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -31,6 +31,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_and", &compileAnd); engine->addCompileFunction(this, "operator_or", &compileOr); engine->addCompileFunction(this, "operator_not", &compileNot); + engine->addCompileFunction(this, "operator_join", &compileJoin); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -89,3 +90,27 @@ CompilerValue *OperatorBlocks::compileNot(Compiler *compiler) { return compiler->createNot(compiler->addInput("OPERAND")); } + +CompilerValue *OperatorBlocks::compileJoin(Compiler *compiler) +{ + auto string1 = compiler->addInput("STRING1"); + auto string2 = compiler->addInput("STRING2"); + return compiler->addFunctionCall("operator_join", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 }); +} + +extern "C" char *operator_join(const char *string1, const char *string2) +{ + const size_t len1 = strlen(string1); + const size_t len2 = strlen(string2); + + char *ret = (char *)malloc((len1 + len2 + 1) * sizeof(char)); + size_t i; + + for (i = 0; i < len1; i++) + ret[i] = string1[i]; + + for (i = 0; i < len2 + 1; i++) // +1: null-terminate + ret[len1 + i] = string2[i]; + + return ret; +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 8570225c..7adadad2 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -29,6 +29,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileAnd(Compiler *compiler); static CompilerValue *compileOr(Compiler *compiler); static CompilerValue *compileNot(Compiler *compiler); + static CompilerValue *compileJoin(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 2993177e..295cd18a 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -353,3 +353,28 @@ TEST_F(OperatorBlocksTest, Not) ASSERT_EQ(Value(values[0]), true); ASSERT_EQ(Value(values[1]), false); } + +TEST_F(OperatorBlocksTest, Join) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_join"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "def"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_join"); + builder.addValueInput("STRING1", "Hello "); + builder.addValueInput("STRING2", "world"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(Value(values[0]), "abcdef"); + ASSERT_EQ(Value(values[1]), "Hello world"); +} From 676cc834ce6e7a8c0da9fb45c2ad69d237d16f14 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Mon, 23 Dec 2024 17:03:45 +0100 Subject: [PATCH 20/25] Implement operator_letter_of --- src/dev/blocks/operatorblocks.cpp | 28 +++++++++++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 43 ++++++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 45bb0f75..4edea1d9 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "operatorblocks.h" @@ -32,6 +33,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_or", &compileOr); engine->addCompileFunction(this, "operator_not", &compileNot); engine->addCompileFunction(this, "operator_join", &compileJoin); + engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -98,6 +100,13 @@ CompilerValue *OperatorBlocks::compileJoin(Compiler *compiler) return compiler->addFunctionCall("operator_join", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 }); } +CompilerValue *OperatorBlocks::compileLetterOf(Compiler *compiler) +{ + auto letter = compiler->addInput("LETTER"); + auto string = compiler->addInput("STRING"); + return compiler->addFunctionCall("operator_letter_of", Compiler::StaticType::String, { Compiler::StaticType::Number, Compiler::StaticType::String }, { letter, string }); +} + extern "C" char *operator_join(const char *string1, const char *string2) { const size_t len1 = strlen(string1); @@ -114,3 +123,22 @@ extern "C" char *operator_join(const char *string1, const char *string2) return ret; } + +extern "C" char *operator_letter_of(double letter, const char *string) +{ + const size_t len = strlen(string); + + if (letter < 1 || letter > len) { + char *ret = (char *)malloc(sizeof(char)); + ret[0] = '\0'; + return ret; + } + + // TODO: Rewrite this + std::u16string u16 = utf8::utf8to16(std::string(string)); + std::string str = utf8::utf16to8(std::u16string({ u16[(size_t)letter - 1] })); + char *ret = (char *)malloc((str.size() + 1) * sizeof(char)); + strcpy(ret, str.c_str()); + + return ret; +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 7adadad2..eef1b5b4 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -30,6 +30,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileOr(Compiler *compiler); static CompilerValue *compileNot(Compiler *compiler); static CompilerValue *compileJoin(Compiler *compiler); + static CompilerValue *compileLetterOf(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 295cd18a..d598bda8 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -378,3 +378,46 @@ TEST_F(OperatorBlocksTest, Join) ASSERT_EQ(Value(values[0]), "abcdef"); ASSERT_EQ(Value(values[1]), "Hello world"); } + +TEST_F(OperatorBlocksTest, LetterOf) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 2); + builder.addValueInput("STRING", "abc"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 7); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 0); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 12); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 1); + builder.addValueInput("STRING", "Ábč"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 5); + ASSERT_EQ(Value(values[0]), "b"); + ASSERT_EQ(Value(values[1]), "w"); + ASSERT_EQ(Value(values[2]), ""); + ASSERT_EQ(Value(values[3]), ""); + ASSERT_EQ(Value(values[4]), "Á"); +} From 84d109cf25f8b01725cd0dcc1661ffc82c276a31 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:01:08 +0100 Subject: [PATCH 21/25] Implement operator_length --- src/dev/blocks/operatorblocks.cpp | 13 +++++++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 28 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 4edea1d9..fb2dfcdb 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -34,6 +34,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_not", &compileNot); engine->addCompileFunction(this, "operator_join", &compileJoin); engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf); + engine->addCompileFunction(this, "operator_length", &compileLength); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -107,6 +108,12 @@ CompilerValue *OperatorBlocks::compileLetterOf(Compiler *compiler) return compiler->addFunctionCall("operator_letter_of", Compiler::StaticType::String, { Compiler::StaticType::Number, Compiler::StaticType::String }, { letter, string }); } +CompilerValue *OperatorBlocks::compileLength(Compiler *compiler) +{ + auto string = compiler->addInput("STRING"); + return compiler->addFunctionCall("operator_length", Compiler::StaticType::Number, { Compiler::StaticType::String }, { string }); +} + extern "C" char *operator_join(const char *string1, const char *string2) { const size_t len1 = strlen(string1); @@ -142,3 +149,9 @@ extern "C" char *operator_letter_of(double letter, const char *string) return ret; } + +extern "C" double operator_length(const char *string) +{ + // TODO: Rewrite this + return utf8::utf8to16(std::string(string)).size(); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index eef1b5b4..bd72e23f 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -31,6 +31,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileNot(Compiler *compiler); static CompilerValue *compileJoin(Compiler *compiler); static CompilerValue *compileLetterOf(Compiler *compiler); + static CompilerValue *compileLength(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index d598bda8..40d482f0 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -421,3 +421,31 @@ TEST_F(OperatorBlocksTest, LetterOf) ASSERT_EQ(Value(values[3]), ""); ASSERT_EQ(Value(values[4]), "Á"); } + +TEST_F(OperatorBlocksTest, Length) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_length"); + builder.addValueInput("STRING", "abc"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_length"); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_length"); + builder.addValueInput("STRING", "dOádčĐaší"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), 3); + ASSERT_EQ(Value(values[1]), 11); + ASSERT_EQ(Value(values[2]), 9); +} From 1454aa556201b0b7bca3968ef09f25c2e6d898cf Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 24 Dec 2024 00:16:20 +0100 Subject: [PATCH 22/25] Implement operator_contains --- src/dev/blocks/operatorblocks.cpp | 18 +++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 61 ++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index fb2dfcdb..eae9a5d8 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -35,6 +35,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_join", &compileJoin); engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf); engine->addCompileFunction(this, "operator_length", &compileLength); + engine->addCompileFunction(this, "operator_contains", &compileContains); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -114,6 +115,13 @@ CompilerValue *OperatorBlocks::compileLength(Compiler *compiler) return compiler->addFunctionCall("operator_length", Compiler::StaticType::Number, { Compiler::StaticType::String }, { string }); } +CompilerValue *OperatorBlocks::compileContains(Compiler *compiler) +{ + auto string1 = compiler->addInput("STRING1"); + auto string2 = compiler->addInput("STRING2"); + return compiler->addFunctionCall("operator_contains", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 }); +} + extern "C" char *operator_join(const char *string1, const char *string2) { const size_t len1 = strlen(string1); @@ -155,3 +163,13 @@ extern "C" double operator_length(const char *string) // TODO: Rewrite this return utf8::utf8to16(std::string(string)).size(); } + +extern "C" bool operator_contains(const char *string1, const char *string2) +{ + // TODO: Rewrite this + std::u16string u16string1 = utf8::utf8to16(std::string(string1)); + std::u16string u16string2 = utf8::utf8to16(std::string(string2)); + std::transform(u16string1.begin(), u16string1.end(), u16string1.begin(), ::tolower); + std::transform(u16string2.begin(), u16string2.end(), u16string2.begin(), ::tolower); + return (u16string1.find(u16string2) != std::u16string::npos); +} diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index bd72e23f..16dbfa6e 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -32,6 +32,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileJoin(Compiler *compiler); static CompilerValue *compileLetterOf(Compiler *compiler); static CompilerValue *compileLength(Compiler *compiler); + static CompilerValue *compileContains(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 40d482f0..d0290f4a 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -449,3 +449,64 @@ TEST_F(OperatorBlocksTest, Length) ASSERT_EQ(Value(values[1]), 11); ASSERT_EQ(Value(values[2]), 9); } + +TEST_F(OperatorBlocksTest, Contains) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "a"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "e"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "C"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "Hello world"); + builder.addValueInput("STRING2", "ello"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "Hello world"); + builder.addValueInput("STRING2", "olld"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "ábČ"); + builder.addValueInput("STRING2", "á"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "ábČ"); + builder.addValueInput("STRING2", "bČ"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "ábČ"); + builder.addValueInput("STRING2", "ďá"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 8); + ASSERT_EQ(Value(values[0]), true); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), true); + ASSERT_EQ(Value(values[3]), true); + ASSERT_EQ(Value(values[4]), false); + ASSERT_EQ(Value(values[5]), true); + ASSERT_EQ(Value(values[6]), true); + ASSERT_EQ(Value(values[7]), false); +} From e237c7be5f48631ba68a405f89828971709d4dfc Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:27:01 +0100 Subject: [PATCH 23/25] Implement operator_mod --- src/dev/blocks/operatorblocks.cpp | 6 ++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 25 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index eae9a5d8..fc2afb1a 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -36,6 +36,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf); engine->addCompileFunction(this, "operator_length", &compileLength); engine->addCompileFunction(this, "operator_contains", &compileContains); + engine->addCompileFunction(this, "operator_mod", &compileMod); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -122,6 +123,11 @@ CompilerValue *OperatorBlocks::compileContains(Compiler *compiler) return compiler->addFunctionCall("operator_contains", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 }); } +CompilerValue *OperatorBlocks::compileMod(Compiler *compiler) +{ + return compiler->createMod(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} + extern "C" char *operator_join(const char *string1, const char *string2) { const size_t len1 = strlen(string1); diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 16dbfa6e..3a365418 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -33,6 +33,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileLetterOf(Compiler *compiler); static CompilerValue *compileLength(Compiler *compiler); static CompilerValue *compileContains(Compiler *compiler); + static CompilerValue *compileMod(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index d0290f4a..277b38da 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -510,3 +510,28 @@ TEST_F(OperatorBlocksTest, Contains) ASSERT_EQ(Value(values[6]), true); ASSERT_EQ(Value(values[7]), false); } + +TEST_F(OperatorBlocksTest, Mod) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_mod"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mod"); + builder.addValueInput("NUM1", -5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 0.7); + ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 1.8); +} From 5ef36d0a175737bd7903c88be638574472d0c466 Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:31:37 +0100 Subject: [PATCH 24/25] Implement operator_round --- src/dev/blocks/operatorblocks.cpp | 6 ++++++ src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 23 +++++++++++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index fc2afb1a..3cbd042a 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -37,6 +37,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_length", &compileLength); engine->addCompileFunction(this, "operator_contains", &compileContains); engine->addCompileFunction(this, "operator_mod", &compileMod); + engine->addCompileFunction(this, "operator_round", &compileRound); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -128,6 +129,11 @@ CompilerValue *OperatorBlocks::compileMod(Compiler *compiler) return compiler->createMod(compiler->addInput("NUM1"), compiler->addInput("NUM2")); } +CompilerValue *OperatorBlocks::compileRound(Compiler *compiler) +{ + return compiler->createRound(compiler->addInput("NUM")); +} + extern "C" char *operator_join(const char *string1, const char *string2) { const size_t len1 = strlen(string1); diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index 3a365418..be39423c 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -34,6 +34,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileLength(Compiler *compiler); static CompilerValue *compileContains(Compiler *compiler); static CompilerValue *compileMod(Compiler *compiler); + static CompilerValue *compileRound(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 277b38da..0947d46d 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -535,3 +535,26 @@ TEST_F(OperatorBlocksTest, Mod) ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 0.7); ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 1.8); } + +TEST_F(OperatorBlocksTest, Round) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_round"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_round"); + builder.addValueInput("NUM", 2.3); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(Value(values[0]), 6); + ASSERT_EQ(Value(values[1]), 2); +} From 833727c720a0114acfb92a8ff718c02ba7f3170d Mon Sep 17 00:00:00 2001 From: adazem009 <68537469+adazem009@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:02:50 +0100 Subject: [PATCH 25/25] Implement operator_mathop --- src/dev/blocks/operatorblocks.cpp | 43 ++++- src/dev/blocks/operatorblocks.h | 1 + test/dev/blocks/operator_blocks_test.cpp | 202 +++++++++++++++++++++++ 3 files changed, 245 insertions(+), 1 deletion(-) diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 3cbd042a..b962d3c7 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -2,7 +2,9 @@ #include #include -#include +#include +#include +#include #include #include "operatorblocks.h" @@ -38,6 +40,7 @@ void OperatorBlocks::registerBlocks(IEngine *engine) engine->addCompileFunction(this, "operator_contains", &compileContains); engine->addCompileFunction(this, "operator_mod", &compileMod); engine->addCompileFunction(this, "operator_round", &compileRound); + engine->addCompileFunction(this, "operator_mathop", &compileMathOp); } CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) @@ -134,6 +137,44 @@ CompilerValue *OperatorBlocks::compileRound(Compiler *compiler) return compiler->createRound(compiler->addInput("NUM")); } +CompilerValue *OperatorBlocks::compileMathOp(Compiler *compiler) +{ + Field *opField = compiler->field("OPERATOR"); + const std::string numInput = "NUM"; + const std::string &op = opField->value().toString(); + + if (op == "abs") + return compiler->createAbs(compiler->addInput(numInput)); + else if (op == "floor") + return compiler->createFloor(compiler->addInput(numInput)); + else if (op == "ceiling") + return compiler->createCeil(compiler->addInput(numInput)); + else if (op == "sqrt") + return compiler->createSqrt(compiler->addInput(numInput)); + else if (op == "sin") + return compiler->createSin(compiler->addInput(numInput)); + else if (op == "cos") + return compiler->createCos(compiler->addInput(numInput)); + else if (op == "tan") + return compiler->createTan(compiler->addInput(numInput)); + else if (op == "asin") + return compiler->createAsin(compiler->addInput(numInput)); + else if (op == "acos") + return compiler->createAcos(compiler->addInput(numInput)); + else if (op == "atan") + return compiler->createAtan(compiler->addInput(numInput)); + else if (op == "ln") + return compiler->createLn(compiler->addInput(numInput)); + else if (op == "log") + return compiler->createLog10(compiler->addInput(numInput)); + else if (op == "e ^") + return compiler->createExp(compiler->addInput(numInput)); + else if (op == "10 ^") + return compiler->createExp10(compiler->addInput(numInput)); + else + return compiler->addConstValue(Value()); +} + extern "C" char *operator_join(const char *string1, const char *string2) { const size_t len1 = strlen(string1); diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index be39423c..1804e931 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -35,6 +35,7 @@ class OperatorBlocks : public IExtension static CompilerValue *compileContains(Compiler *compiler); static CompilerValue *compileMod(Compiler *compiler); static CompilerValue *compileRound(Compiler *compiler); + static CompilerValue *compileMathOp(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/test/dev/blocks/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index 0947d46d..c67bde95 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -558,3 +558,205 @@ TEST_F(OperatorBlocksTest, Round) ASSERT_EQ(Value(values[0]), 6); ASSERT_EQ(Value(values[1]), 2); } + +TEST_F(OperatorBlocksTest, MathOp) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + // abs + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "abs"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "abs"); + builder.addValueInput("NUM", -5.7); + builder.captureBlockReturnValue(); + + // floor + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "floor"); + builder.addValueInput("NUM", 3.2); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "floor"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + // ceiling + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ceiling"); + builder.addValueInput("NUM", 3.2); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ceiling"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + // sqrt + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sqrt"); + builder.addValueInput("NUM", 16); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sqrt"); + builder.addValueInput("NUM", 2); + builder.captureBlockReturnValue(); + + // sin + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sin"); + builder.addValueInput("NUM", 90); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sin"); + builder.addValueInput("NUM", 30); + builder.captureBlockReturnValue(); + + // cos + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "cos"); + builder.addValueInput("NUM", 0); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "cos"); + builder.addValueInput("NUM", 60); + builder.captureBlockReturnValue(); + + // tan + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "tan"); + builder.addValueInput("NUM", 30); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "tan"); + builder.addValueInput("NUM", 45); + builder.captureBlockReturnValue(); + + // asin + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "asin"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "asin"); + builder.addValueInput("NUM", 0.5); + builder.captureBlockReturnValue(); + + // acos + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "acos"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "acos"); + builder.addValueInput("NUM", 0.5); + builder.captureBlockReturnValue(); + + // atan + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "atan"); + builder.addValueInput("NUM", 0.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "atan"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + // ln + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ln"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ln"); + builder.addValueInput("NUM", 10); + builder.captureBlockReturnValue(); + + // log + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "log"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "log"); + builder.addValueInput("NUM", 100); + builder.captureBlockReturnValue(); + + // e ^ + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "e ^"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "e ^"); + builder.addValueInput("NUM", 8.2); + builder.captureBlockReturnValue(); + + // 10 ^ + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "10 ^"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "10 ^"); + builder.addValueInput("NUM", 8.2); + builder.captureBlockReturnValue(); + + // invalid + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "invalid"); + builder.addValueInput("NUM", -5.54); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 29); + ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 5.7); + ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 5.7); + ASSERT_EQ(std::round(value_toDouble(&values[2]) * 100) / 100, 3); + ASSERT_EQ(std::round(value_toDouble(&values[3]) * 100) / 100, 5); + ASSERT_EQ(std::round(value_toDouble(&values[4]) * 100) / 100, 4); + ASSERT_EQ(std::round(value_toDouble(&values[5]) * 100) / 100, 6); + ASSERT_EQ(std::round(value_toDouble(&values[6]) * 100) / 100, 4); + ASSERT_EQ(std::round(value_toDouble(&values[7]) * 100) / 100, 1.41); + ASSERT_EQ(std::round(value_toDouble(&values[8]) * 100) / 100, 1); + ASSERT_EQ(std::round(value_toDouble(&values[9]) * 100) / 100, 0.5); + ASSERT_EQ(std::round(value_toDouble(&values[10]) * 100) / 100, 1); + ASSERT_EQ(std::round(value_toDouble(&values[11]) * 100) / 100, 0.5); + ASSERT_EQ(std::round(value_toDouble(&values[12]) * 100) / 100, 0.58); + ASSERT_EQ(std::round(value_toDouble(&values[13]) * 100) / 100, 1); + ASSERT_EQ(std::round(value_toDouble(&values[14]) * 100) / 100, 90); + ASSERT_EQ(std::round(value_toDouble(&values[15]) * 100) / 100, 30); + ASSERT_EQ(std::round(value_toDouble(&values[16]) * 100) / 100, 0); + ASSERT_EQ(std::round(value_toDouble(&values[17]) * 100) / 100, 60); + ASSERT_EQ(std::round(value_toDouble(&values[18]) * 100) / 100, 26.57); + ASSERT_EQ(std::round(value_toDouble(&values[19]) * 100) / 100, 45); + ASSERT_EQ(std::round(value_toDouble(&values[20]) * 100) / 100, 0); + ASSERT_EQ(std::round(value_toDouble(&values[21]) * 100) / 100, 2.3); + ASSERT_EQ(std::round(value_toDouble(&values[22]) * 100) / 100, 0); + ASSERT_EQ(std::round(value_toDouble(&values[23]) * 100) / 100, 2); + ASSERT_EQ(std::round(value_toDouble(&values[24]) * 100) / 100, 2.72); + ASSERT_EQ(std::round(value_toDouble(&values[25]) * 100) / 100, 3640.95); + ASSERT_EQ(std::round(value_toDouble(&values[26]) * 100) / 100, 10); + ASSERT_EQ(std::round(value_toDouble(&values[27]) * 100) / 100, 158489319.25); + ASSERT_EQ(std::round(value_toDouble(&values[28]) * 100) / 100, 0); +}