diff --git a/CMakeLists.txt b/CMakeLists.txt index 1aaebf77..95f74700 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,6 +76,7 @@ if (LIBSCRATCHCPP_USE_LLVM) include/scratchcpp/dev/compiler.h include/scratchcpp/dev/compilervalue.h include/scratchcpp/dev/compilerconstant.h + include/scratchcpp/dev/compilerlocalvariable.h include/scratchcpp/dev/executablecode.h include/scratchcpp/dev/executioncontext.h include/scratchcpp/dev/promise.h diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h index eb70df4d..4486099e 100644 --- a/include/scratchcpp/dev/compiler.h +++ b/include/scratchcpp/dev/compiler.h @@ -16,6 +16,7 @@ class Target; class ExecutableCode; class CompilerValue; class CompilerConstant; +class CompilerLocalVariable; class Variable; class List; class Input; @@ -52,6 +53,7 @@ class LIBSCRATCHCPP_EXPORT Compiler CompilerValue *addFunctionCallWithCtx(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {}); CompilerConstant *addConstValue(const Value &value); CompilerValue *addLoopIndex(); + CompilerValue *addLocalVariableValue(CompilerLocalVariable *variable); CompilerValue *addVariableValue(Variable *variable); CompilerValue *addListContents(List *list); CompilerValue *addListItem(List *list, CompilerValue *index); @@ -66,6 +68,7 @@ class LIBSCRATCHCPP_EXPORT Compiler CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2); CompilerValue *createRandom(CompilerValue *from, CompilerValue *to); + CompilerValue *createRandomInt(CompilerValue *from, CompilerValue *to); CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2); CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2); @@ -94,6 +97,9 @@ class LIBSCRATCHCPP_EXPORT Compiler CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType); + CompilerLocalVariable *createLocalVariable(Compiler::StaticType type); + void createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value); + void createVariableWrite(Variable *variable, CompilerValue *value); void createListClear(List *list); diff --git a/include/scratchcpp/dev/compilerlocalvariable.h b/include/scratchcpp/dev/compilerlocalvariable.h new file mode 100644 index 00000000..455a3ae4 --- /dev/null +++ b/include/scratchcpp/dev/compilerlocalvariable.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "compiler.h" + +namespace libscratchcpp +{ + +class CompilerLocalVariablePrivate; + +/*! \brief The CompilerLocalVariable class represents a statically typed local variable in compiled code. */ +class LIBSCRATCHCPP_EXPORT CompilerLocalVariable +{ + public: + CompilerLocalVariable(CompilerValue *ptr); + CompilerLocalVariable(const CompilerLocalVariable &) = delete; + + CompilerValue *ptr() const; + Compiler::StaticType type() const; + + private: + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h index 65a3383c..e2045a2e 100644 --- a/include/scratchcpp/dev/test/scriptbuilder.h +++ b/include/scratchcpp/dev/test/scriptbuilder.h @@ -31,7 +31,6 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder ~ScriptBuilder(); void addBlock(const std::string &opcode); - void addReporterBlock(const std::string &opcode); void captureBlockReturnValue(); void addValueInput(const std::string &name, const Value &value); @@ -47,6 +46,7 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder void addEntityField(const std::string &name, std::shared_ptr entity); std::shared_ptr currentBlock(); + std::shared_ptr takeBlock(); void build(); void run(); diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp index 8b4602ad..4da342d3 100644 --- a/src/dev/blocks/listblocks.cpp +++ b/src/dev/blocks/listblocks.cpp @@ -1,5 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + #include "listblocks.h" using namespace libscratchcpp; @@ -16,4 +22,191 @@ std::string ListBlocks::description() const void ListBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "data_addtolist", &compileAddToList); + engine->addCompileFunction(this, "data_deleteoflist", &compileDeleteOfList); + engine->addCompileFunction(this, "data_deletealloflist", &compileDeleteAllOfList); + engine->addCompileFunction(this, "data_insertatlist", &compileInsertAtList); + engine->addCompileFunction(this, "data_replaceitemoflist", &compileReplaceItemOfList); + engine->addCompileFunction(this, "data_itemoflist", &compileItemOfList); + engine->addCompileFunction(this, "data_itemnumoflist", &compileItemNumOfList); + engine->addCompileFunction(this, "data_lengthoflist", &compileLengthOfList); + engine->addCompileFunction(this, "data_listcontainsitem", &compileListContainsItem); +} + +CompilerValue *ListBlocks::compileAddToList(Compiler *compiler) +{ + auto list = compiler->field("LIST")->valuePtr(); + assert(list); + + if (list) + compiler->createListAppend(static_cast(list.get()), compiler->addInput("ITEM")); + + return nullptr; +} + +CompilerValue *ListBlocks::getListIndex(Compiler *compiler, CompilerValue *input, List *list, CompilerValue *listSize) +{ + CompilerLocalVariable *ret = compiler->createLocalVariable(Compiler::StaticType::Number); + + CompilerValue *isRandom1 = compiler->createCmpEQ(input, compiler->addConstValue("random")); + CompilerValue *isRandom2 = compiler->createCmpEQ(input, compiler->addConstValue("any")); + CompilerValue *isRandom = compiler->createOr(isRandom1, isRandom2); + + compiler->beginIfStatement(isRandom); + { + CompilerValue *random = compiler->createRandomInt(compiler->addConstValue(1), listSize); + compiler->createLocalVariableWrite(ret, random); + } + compiler->beginElseBranch(); + { + CompilerValue *isLast = compiler->createCmpEQ(input, compiler->addConstValue("last")); + compiler->createLocalVariableWrite(ret, compiler->createSelect(isLast, listSize, input, Compiler::StaticType::Number)); + } + compiler->endIf(); + + return compiler->addLocalVariableValue(ret); +} + +CompilerValue *ListBlocks::compileDeleteOfList(Compiler *compiler) +{ + List *list = static_cast(compiler->field("LIST")->valuePtr().get()); + assert(list); + + if (list) { + CompilerValue *index = compiler->addInput("INDEX"); + CompilerValue *cond = compiler->createCmpEQ(index, compiler->addConstValue("all")); + compiler->beginIfStatement(cond); + { + compiler->createListClear(list); + } + compiler->beginElseBranch(); + { + CompilerValue *min = compiler->addConstValue(-1); + CompilerValue *max = compiler->addListSize(list); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max)); + compiler->beginIfStatement(cond); + { + compiler->createListRemove(list, index); + } + compiler->endIf(); + } + compiler->endIf(); + } + + return nullptr; +} + +CompilerValue *ListBlocks::compileDeleteAllOfList(Compiler *compiler) +{ + auto list = compiler->field("LIST")->valuePtr(); + assert(list); + + if (list) + compiler->createListClear(static_cast(list.get())); + + return nullptr; +} + +CompilerValue *ListBlocks::compileInsertAtList(Compiler *compiler) +{ + List *list = static_cast(compiler->field("LIST")->valuePtr().get()); + assert(list); + + if (list) { + CompilerValue *index = compiler->addInput("INDEX"); + CompilerValue *min = compiler->addConstValue(-1); + CompilerValue *max = compiler->createAdd(compiler->addListSize(list), compiler->addConstValue(1)); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max)); + compiler->beginIfStatement(cond); + { + CompilerValue *item = compiler->addInput("ITEM"); + compiler->createListInsert(list, index, item); + } + compiler->endIf(); + } + + return nullptr; +} + +CompilerValue *ListBlocks::compileReplaceItemOfList(Compiler *compiler) +{ + List *list = static_cast(compiler->field("LIST")->valuePtr().get()); + assert(list); + + if (list) { + CompilerValue *index = compiler->addInput("INDEX"); + CompilerValue *min = compiler->addConstValue(-1); + CompilerValue *max = compiler->addListSize(list); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max)); + compiler->beginIfStatement(cond); + { + CompilerValue *item = compiler->addInput("ITEM"); + compiler->createListReplace(list, index, item); + } + compiler->endIf(); + } + + return nullptr; +} + +CompilerValue *ListBlocks::compileItemOfList(Compiler *compiler) +{ + List *list = static_cast(compiler->field("LIST")->valuePtr().get()); + assert(list); + + if (list) { + CompilerValue *index = compiler->addInput("INDEX"); + CompilerValue *min = compiler->addConstValue(-1); + CompilerValue *max = compiler->addListSize(list); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + CompilerValue *cond = compiler->createAnd(compiler->createCmpGT(index, min), compiler->createCmpLT(index, max)); + CompilerValue *item = compiler->addListItem(list, index); + return compiler->createSelect(cond, item, compiler->addConstValue(Value()), Compiler::StaticType::Unknown); + } + + return nullptr; +} + +CompilerValue *ListBlocks::compileItemNumOfList(Compiler *compiler) +{ + List *list = static_cast(compiler->field("LIST")->valuePtr().get()); + assert(list); + + if (list) { + CompilerValue *item = compiler->addInput("ITEM"); + return compiler->createAdd(compiler->addListItemIndex(list, item), compiler->addConstValue(1)); + } + + return nullptr; +} + +CompilerValue *ListBlocks::compileLengthOfList(Compiler *compiler) +{ + List *list = static_cast(compiler->field("LIST")->valuePtr().get()); + assert(list); + + if (list) + return compiler->addListSize(list); + + return nullptr; +} + +CompilerValue *ListBlocks::compileListContainsItem(Compiler *compiler) +{ + List *list = static_cast(compiler->field("LIST")->valuePtr().get()); + assert(list); + + if (list) { + CompilerValue *item = compiler->addInput("ITEM"); + return compiler->addListContains(list, item); + } + + return nullptr; } diff --git a/src/dev/blocks/listblocks.h b/src/dev/blocks/listblocks.h index edf01e8c..3a8d0126 100644 --- a/src/dev/blocks/listblocks.h +++ b/src/dev/blocks/listblocks.h @@ -7,6 +7,8 @@ namespace libscratchcpp { +class List; + class ListBlocks : public IExtension { public: @@ -14,6 +16,18 @@ class ListBlocks : public IExtension std::string description() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileAddToList(Compiler *compiler); + static CompilerValue *getListIndex(Compiler *compiler, CompilerValue *input, List *list, CompilerValue *listSize); + static CompilerValue *compileDeleteOfList(Compiler *compiler); + static CompilerValue *compileDeleteAllOfList(Compiler *compiler); + static CompilerValue *compileInsertAtList(Compiler *compiler); + static CompilerValue *compileReplaceItemOfList(Compiler *compiler); + static CompilerValue *compileItemOfList(Compiler *compiler); + static CompilerValue *compileItemNumOfList(Compiler *compiler); + static CompilerValue *compileLengthOfList(Compiler *compiler); + static CompilerValue *compileListContainsItem(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt index a3e2790e..9320b664 100644 --- a/src/dev/engine/CMakeLists.txt +++ b/src/dev/engine/CMakeLists.txt @@ -9,6 +9,9 @@ target_sources(scratchcpp compilerconstant.cpp compilerconstant_p.cpp compilerconstant_p.h + compilerlocalvariable.cpp + compilerlocalvariable_p.cpp + compilerlocalvariable_p.h executioncontext.cpp executioncontext_p.cpp executioncontext_p.h diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp index 807fa11f..bcbc8a35 100644 --- a/src/dev/engine/compiler.cpp +++ b/src/dev/engine/compiler.cpp @@ -127,6 +127,12 @@ CompilerValue *Compiler::addLoopIndex() return impl->builder->addLoopIndex(); } +/*! Adds the value of the given local variable to the code. */ +CompilerValue *Compiler::addLocalVariableValue(CompilerLocalVariable *variable) +{ + return impl->builder->addLocalVariableValue(variable); +} + /*! Adds the value of the given variable to the code. */ CompilerValue *Compiler::addVariableValue(Variable *variable) { @@ -199,6 +205,15 @@ CompilerValue *Compiler::createRandom(CompilerValue *from, CompilerValue *to) return impl->builder->createRandom(from, to); } +/*! + * Creates a random integer instruction. + * \note Infinity or NaN results in undefined behavior. + */ +CompilerValue *Compiler::createRandomInt(CompilerValue *from, CompilerValue *to) +{ + return impl->builder->createRandomInt(from, to); +} + /*! Creates an equality comparison instruction. */ CompilerValue *Compiler::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) { @@ -337,6 +352,18 @@ CompilerValue *Compiler::createSelect(CompilerValue *cond, CompilerValue *trueVa return impl->builder->createSelect(cond, trueValue, falseValue, valueType); } +/*! Creates a local variable with the given type. */ +CompilerLocalVariable *Compiler::createLocalVariable(StaticType type) +{ + return impl->builder->createLocalVariable(type); +} + +/*! Creates a local variable write operation. */ +void Compiler::createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value) +{ + impl->builder->createLocalVariableWrite(variable, value); +} + /*! Creates a variable write operation. */ void Compiler::createVariableWrite(Variable *variable, CompilerValue *value) { diff --git a/src/dev/engine/compilerlocalvariable.cpp b/src/dev/engine/compilerlocalvariable.cpp new file mode 100644 index 00000000..8a8329a4 --- /dev/null +++ b/src/dev/engine/compilerlocalvariable.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "compilerlocalvariable_p.h" + +using namespace libscratchcpp; + +CompilerLocalVariable::CompilerLocalVariable(CompilerValue *ptr) : + impl(spimpl::make_unique_impl(ptr)) +{ +} + +CompilerValue *CompilerLocalVariable::ptr() const +{ + return impl->ptr; +} + +Compiler::StaticType CompilerLocalVariable::type() const +{ + return impl->ptr->type(); +} diff --git a/src/dev/engine/compilerlocalvariable_p.cpp b/src/dev/engine/compilerlocalvariable_p.cpp new file mode 100644 index 00000000..3de1c615 --- /dev/null +++ b/src/dev/engine/compilerlocalvariable_p.cpp @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "compilerlocalvariable_p.h" + +using namespace libscratchcpp; + +CompilerLocalVariablePrivate::CompilerLocalVariablePrivate(CompilerValue *ptr) : + ptr(ptr) +{ + assert(ptr); +} diff --git a/src/dev/engine/compilerlocalvariable_p.h b/src/dev/engine/compilerlocalvariable_p.h new file mode 100644 index 00000000..f1872668 --- /dev/null +++ b/src/dev/engine/compilerlocalvariable_p.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace libscratchcpp +{ + +class CompilerValue; + +struct CompilerLocalVariablePrivate +{ + CompilerLocalVariablePrivate(CompilerValue *ptr); + CompilerLocalVariablePrivate(CompilerLocalVariablePrivate &) = delete; + + CompilerValue *ptr = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h index d65a24f0..a3d38629 100644 --- a/src/dev/engine/internal/icodebuilder.h +++ b/src/dev/engine/internal/icodebuilder.h @@ -24,6 +24,7 @@ class ICodeBuilder virtual CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0; virtual CompilerConstant *addConstValue(const Value &value) = 0; virtual CompilerValue *addLoopIndex() = 0; + virtual CompilerValue *addLocalVariableValue(CompilerLocalVariable *variable) = 0; virtual CompilerValue *addVariableValue(Variable *variable) = 0; virtual CompilerValue *addListContents(List *list) = 0; virtual CompilerValue *addListItem(List *list, CompilerValue *index) = 0; @@ -37,6 +38,7 @@ class ICodeBuilder virtual CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) = 0; virtual CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) = 0; + virtual CompilerValue *createRandomInt(CompilerValue *from, CompilerValue *to) = 0; virtual CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) = 0; virtual CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) = 0; @@ -65,6 +67,9 @@ class ICodeBuilder virtual CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) = 0; + virtual CompilerLocalVariable *createLocalVariable(Compiler::StaticType type) = 0; + virtual void createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value) = 0; + virtual void createVariableWrite(Variable *variable, CompilerValue *value) = 0; virtual void createListClear(List *list) = 0; diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp index 74698bf2..98d4190b 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "llvmcodebuilder.h" #include "llvmexecutablecode.h" @@ -145,7 +146,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() step.functionReturnReg->value = ret; if (step.functionReturnReg->type() == Compiler::StaticType::String) - m_heap.push_back(step.functionReturnReg->value); + freeLater(step.functionReturnReg->value); } break; @@ -223,6 +224,16 @@ std::shared_ptr LLVMCodeBuilder::finalize() break; } + case LLVMInstruction::Type::RandomInt: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *from = m_builder.CreateFPToSI(castValue(arg1.second, arg1.first), m_builder.getInt64Ty()); + llvm::Value *to = m_builder.CreateFPToSI(castValue(arg2.second, arg2.first), m_builder.getInt64Ty()); + step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_long(), { executionContextPtr, from, to }); + break; + } + case LLVMInstruction::Type::CmpEQ: { assert(step.args.size() == 2); const auto &arg1 = step.args[0].second; @@ -494,13 +505,81 @@ std::shared_ptr LLVMCodeBuilder::finalize() const auto &arg1 = step.args[0]; const auto &arg2 = step.args[1]; const auto &arg3 = step.args[2]; + auto type = arg2.first; llvm::Value *cond = castValue(arg1.second, arg1.first); - llvm::Value *trueValue = castValue(arg2.second, arg2.first); - llvm::Value *falseValue = castValue(arg3.second, arg3.first); + llvm::Value *trueValue; + llvm::Value *falseValue; + + if (type == Compiler::StaticType::Unknown) { + trueValue = createValue(arg2.second); + falseValue = createValue(arg3.second); + } else { + trueValue = castValue(arg2.second, type); + falseValue = castValue(arg3.second, type); + } + step.functionReturnReg->value = m_builder.CreateSelect(cond, trueValue, falseValue); break; } + case LLVMInstruction::Type::CreateLocalVariable: { + assert(step.args.empty()); + llvm::Type *type = nullptr; + + switch (step.functionReturnReg->type()) { + case Compiler::StaticType::Number: + type = m_builder.getDoubleTy(); + break; + + case Compiler::StaticType::Bool: + type = m_builder.getInt1Ty(); + break; + + case Compiler::StaticType::String: + std::cerr << "error: local variables do not support string type" << std::endl; + break; + + default: + assert(false); + break; + } + + step.functionReturnReg->value = m_builder.CreateAlloca(type); + break; + } + + case LLVMInstruction::Type::WriteLocalVariable: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *converted = castValue(arg2.second, arg2.first); + m_builder.CreateStore(converted, arg1.second->value); + break; + } + + case LLVMInstruction::Type::ReadLocalVariable: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + llvm::Type *type = nullptr; + + switch (step.functionReturnReg->type()) { + case Compiler::StaticType::Number: + type = m_builder.getDoubleTy(); + break; + + case Compiler::StaticType::Bool: + type = m_builder.getInt1Ty(); + break; + + default: + assert(false); + break; + } + + step.functionReturnReg->value = m_builder.CreateLoad(type, arg.second->value); + break; + } + case LLVMInstruction::Type::WriteVariable: { assert(step.args.size() == 1); assert(m_variablePtrs.find(step.workVariable) != m_variablePtrs.cend()); @@ -670,7 +749,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() assert(step.args.size() == 0); const LLVMListPtr &listPtr = m_listPtrs[step.workList]; llvm::Value *ptr = m_builder.CreateCall(resolve_list_to_string(), listPtr.ptr); - m_heap.push_back(ptr); // deallocate later + freeLater(ptr); step.functionReturnReg->value = ptr; break; } @@ -712,7 +791,8 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::Yield: if (!m_warp) { - freeHeap(); + // TODO: Do not allow use after suspend (use after free) + freeScopeHeap(); syncVariables(targetVariables); coro->createSuspend(); reloadVariables(targetVariables); @@ -730,7 +810,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() assert(step.args.size() == 1); const auto ® = step.args[0]; assert(reg.first == Compiler::StaticType::Bool); - freeHeap(); statement.condition = castValue(reg.second, reg.first); // Switch to body branch @@ -757,7 +836,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Jump to the branch after the if statement assert(!statement.afterIf); statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func); - freeHeap(); + freeScopeHeap(); m_builder.CreateBr(statement.afterIf); // Create else branch @@ -776,12 +855,12 @@ std::shared_ptr LLVMCodeBuilder::finalize() case LLVMInstruction::Type::EndIf: { assert(!ifStatements.empty()); LLVMIfStatement &statement = ifStatements.back(); + freeScopeHeap(); // Jump to the branch after the if statement if (!statement.afterIf) statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", func); - freeHeap(); m_builder.CreateBr(statement.afterIf); if (statement.elseBranch) { @@ -822,7 +901,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() // Clamp count if <= 0 (we can skip the loop if count is not positive) llvm::Value *comparison = m_builder.CreateFCmpULE(count, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0))); - freeHeap(); m_builder.CreateCondBr(comparison, loop.afterLoop, roundBranch); // Round (Scratch-specific behavior) @@ -876,7 +954,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() const auto ® = step.args[0]; assert(reg.first == Compiler::StaticType::Bool); llvm::Value *condition = castValue(reg.second, reg.first); - freeHeap(); m_builder.CreateCondBr(condition, body, loop.afterLoop); // Switch to body branch @@ -898,7 +975,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() const auto ® = step.args[0]; assert(reg.first == Compiler::StaticType::Bool); llvm::Value *condition = castValue(reg.second, reg.first); - freeHeap(); m_builder.CreateCondBr(condition, loop.afterLoop, body); // Switch to body branch @@ -911,7 +987,6 @@ std::shared_ptr LLVMCodeBuilder::finalize() LLVMLoop loop; loop.isRepeatLoop = false; loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func); - freeHeap(); m_builder.CreateBr(loop.conditionBranch); m_builder.SetInsertPoint(loop.conditionBranch); loops.push_back(loop); @@ -930,7 +1005,7 @@ std::shared_ptr LLVMCodeBuilder::finalize() } // Jump to the condition branch - freeHeap(); + freeScopeHeap(); m_builder.CreateBr(loop.conditionBranch); // Switch to the branch after the loop @@ -953,7 +1028,8 @@ std::shared_ptr LLVMCodeBuilder::finalize() m_builder.CreateBr(endBranch); m_builder.SetInsertPoint(endBranch); - freeHeap(); + assert(m_heap.size() == 1); + freeScopeHeap(); syncVariables(targetVariables); // End and verify the function @@ -1031,7 +1107,7 @@ CompilerConstant *LLVMCodeBuilder::addConstValue(const Value &value) { auto constReg = std::make_shared(TYPE_MAP[value.type()], value); auto reg = std::reinterpret_pointer_cast(constReg); - return static_cast(addReg(reg)); + return static_cast(static_cast(addReg(reg))); } CompilerValue *LLVMCodeBuilder::addLoopIndex() @@ -1039,6 +1115,11 @@ CompilerValue *LLVMCodeBuilder::addLoopIndex() return createOp(LLVMInstruction::Type::LoopIndex, Compiler::StaticType::Number, {}, {}); } +CompilerValue *LLVMCodeBuilder::addLocalVariableValue(CompilerLocalVariable *variable) +{ + return createOp(LLVMInstruction::Type::ReadLocalVariable, variable->type(), variable->type(), { variable->ptr() }); +} + CompilerValue *LLVMCodeBuilder::addVariableValue(Variable *variable) { LLVMInstruction ins(LLVMInstruction::Type::ReadVariable); @@ -1126,6 +1207,11 @@ CompilerValue *LLVMCodeBuilder::createRandom(CompilerValue *from, CompilerValue return createOp(LLVMInstruction::Type::Random, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { from, to }); } +CompilerValue *LLVMCodeBuilder::createRandomInt(CompilerValue *from, CompilerValue *to) +{ + return createOp(LLVMInstruction::Type::RandomInt, Compiler::StaticType::Number, Compiler::StaticType::Number, { from, to }); +} + CompilerValue *LLVMCodeBuilder::createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) { return createOp(LLVMInstruction::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 }); @@ -1238,7 +1324,25 @@ CompilerValue *LLVMCodeBuilder::createExp10(CompilerValue *num) CompilerValue *LLVMCodeBuilder::createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) { - return createOp(LLVMInstruction::Type::Select, valueType, { Compiler::StaticType::Bool, valueType, valueType }, { cond, trueValue, falseValue }); + LLVMRegister *ret = createOp(LLVMInstruction::Type::Select, valueType, { Compiler::StaticType::Bool, valueType, valueType }, { cond, trueValue, falseValue }); + + if (valueType == Compiler::StaticType::Unknown) + ret->isRawValue = false; + + return ret; +} + +CompilerLocalVariable *LLVMCodeBuilder::createLocalVariable(Compiler::StaticType type) +{ + CompilerValue *ptr = createOp(LLVMInstruction::Type::CreateLocalVariable, type); + auto var = std::make_shared(ptr); + m_localVars.push_back(var); + return var.get(); +} + +void LLVMCodeBuilder::createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value) +{ + createOp(LLVMInstruction::Type::WriteLocalVariable, Compiler::StaticType::Void, variable->type(), { variable->ptr(), value }); } void LLVMCodeBuilder::createVariableWrite(Variable *variable, CompilerValue *value) @@ -1428,6 +1532,8 @@ void LLVMCodeBuilder::pushScopeLevel() m_scopeLists.push_back(listTypes); } else m_scopeLists.push_back(m_scopeLists.back()); + + m_heap.push_back({}); } void LLVMCodeBuilder::popScopeLevel() @@ -1449,6 +1555,9 @@ void LLVMCodeBuilder::popScopeLevel() } m_scopeLists.pop_back(); + + freeScopeHeap(); + m_heap.pop_back(); } void LLVMCodeBuilder::verifyFunction(llvm::Function *func) @@ -1477,19 +1586,34 @@ void LLVMCodeBuilder::optimize() modulePassManager.run(*m_module, moduleAnalysisManager); } -CompilerValue *LLVMCodeBuilder::addReg(std::shared_ptr reg) +LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr reg) { m_regs.push_back(reg); return reg.get(); } -void LLVMCodeBuilder::freeHeap() +void LLVMCodeBuilder::freeLater(llvm::Value *value) +{ + assert(!m_heap.empty()); + + if (m_heap.empty()) + return; + + m_heap.back().push_back(value); +} + +void LLVMCodeBuilder::freeScopeHeap() { - // Free dynamically allocated memory - for (llvm::Value *ptr : m_heap) + if (m_heap.empty()) + return; + + // Free dynamically allocated memory in current scope + auto &heap = m_heap.back(); + + for (llvm::Value *ptr : heap) m_builder.CreateFree(ptr); - m_heap.clear(); + heap.clear(); } llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType targetType) @@ -1561,7 +1685,7 @@ llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType case Compiler::StaticType::Unknown: { // Cast to string llvm::Value *ptr = m_builder.CreateCall(resolve_value_toCString(), reg->value); - m_heap.push_back(ptr); // deallocate later + freeLater(ptr); return ptr; } @@ -1624,7 +1748,7 @@ llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegister *reg, Compiler::StaticTy case Compiler::StaticType::Number: { // Convert double to string llvm::Value *ptr = m_builder.CreateCall(resolve_value_doubleToCString(), reg->value); - m_heap.push_back(ptr); // deallocate later + freeLater(ptr); return ptr; } @@ -1783,7 +1907,7 @@ void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr, llvm::Functi m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); } -CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) +LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) { std::vector types; types.reserve(args.size()); @@ -1794,7 +1918,7 @@ CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::S return createOp(ins, retType, types, args); } -CompilerValue *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) { m_instructions.push_back(ins); LLVMInstruction &createdIns = m_instructions.back(); @@ -2384,6 +2508,12 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double() return resolveFunction("llvm_random_double", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getDoubleTy(), m_builder.getDoubleTy() }, false)); } +llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_long() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); + return resolveFunction("llvm_random_long", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt64Ty(), m_builder.getInt64Ty() }, false)); +} + llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_bool() { llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h index 62e4aadf..91f42e80 100644 --- a/src/dev/engine/internal/llvm/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -31,6 +31,7 @@ class LLVMCodeBuilder : public ICodeBuilder CompilerValue *addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override; CompilerConstant *addConstValue(const Value &value) override; CompilerValue *addLoopIndex() override; + CompilerValue *addLocalVariableValue(CompilerLocalVariable *variable) override; CompilerValue *addVariableValue(Variable *variable) override; CompilerValue *addListContents(List *list) override; CompilerValue *addListItem(List *list, CompilerValue *index) override; @@ -44,6 +45,7 @@ class LLVMCodeBuilder : public ICodeBuilder CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) override; CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) override; + CompilerValue *createRandomInt(CompilerValue *from, CompilerValue *to) override; CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) override; CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) override; @@ -72,6 +74,9 @@ class LLVMCodeBuilder : public ICodeBuilder CompilerValue *createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) override; + CompilerLocalVariable *createLocalVariable(Compiler::StaticType type) override; + void createLocalVariableWrite(CompilerLocalVariable *variable, CompilerValue *value) override; + void createVariableWrite(Variable *variable, CompilerValue *value) override; void createListClear(List *list) override; @@ -111,9 +116,10 @@ class LLVMCodeBuilder : public ICodeBuilder void verifyFunction(llvm::Function *func); void optimize(); - CompilerValue *addReg(std::shared_ptr reg); + LLVMRegister *addReg(std::shared_ptr reg); - void freeHeap(); + void freeLater(llvm::Value *value); + void freeScopeHeap(); llvm::Value *castValue(LLVMRegister *reg, Compiler::StaticType targetType); llvm::Value *castRawValue(LLVMRegister *reg, Compiler::StaticType targetType); llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType); @@ -129,8 +135,8 @@ class LLVMCodeBuilder : public ICodeBuilder void reloadLists(); void updateListDataPtr(const LLVMListPtr &listPtr, llvm::Function *func); - CompilerValue *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); - CompilerValue *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); + LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); + LLVMRegister *createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes = {}, const Compiler::Args &args = {}); void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType); @@ -170,6 +176,7 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::FunctionCallee resolve_list_to_string(); llvm::FunctionCallee resolve_llvm_random(); llvm::FunctionCallee resolve_llvm_random_double(); + llvm::FunctionCallee resolve_llvm_random_long(); llvm::FunctionCallee resolve_llvm_random_bool(); llvm::FunctionCallee resolve_strcasecmp(); @@ -192,10 +199,11 @@ class LLVMCodeBuilder : public ICodeBuilder std::vector m_instructions; std::vector> m_regs; + std::vector> m_localVars; bool m_defaultWarp = false; bool m_warp = false; - std::vector m_heap; + std::vector> m_heap; // scopes std::shared_ptr m_output; }; diff --git a/src/dev/engine/internal/llvm/llvmfunctions.cpp b/src/dev/engine/internal/llvm/llvmfunctions.cpp index d07be391..eafd9a8c 100644 --- a/src/dev/engine/internal/llvm/llvmfunctions.cpp +++ b/src/dev/engine/internal/llvm/llvmfunctions.cpp @@ -19,6 +19,11 @@ extern "C" return value_doubleIsInt(from) && value_doubleIsInt(to) ? ctx->rng()->randint(from, to) : ctx->rng()->randintDouble(from, to); } + double llvm_random_long(ExecutionContext *ctx, long from, long to) + { + return ctx->rng()->randint(from, to); + } + double llvm_random_bool(ExecutionContext *ctx, bool from, bool to) { return ctx->rng()->randint(from, to); diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h index f3d2fa7b..f4518142 100644 --- a/src/dev/engine/internal/llvm/llvminstruction.h +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -19,6 +19,7 @@ struct LLVMInstruction Mul, Div, Random, + RandomInt, CmpEQ, CmpGT, CmpLT, @@ -42,6 +43,9 @@ struct LLVMInstruction Exp, Exp10, Select, + CreateLocalVariable, + WriteLocalVariable, + ReadLocalVariable, WriteVariable, ReadVariable, ClearList, diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp index ea232b07..641b3d0e 100644 --- a/src/dev/test/scriptbuilder.cpp +++ b/src/dev/test/scriptbuilder.cpp @@ -59,19 +59,13 @@ void ScriptBuilder::addBlock(const std::string &opcode) addBlock(impl->lastBlock); } -/*! Creates a reporter block with the given opcode to be used with captureBlockReturnValue() later. */ -void ScriptBuilder::addReporterBlock(const std::string &opcode) -{ - impl->lastBlock = std::make_shared(std::to_string(impl->blockId++), opcode); -} - /*! Captures the return value of the created reporter block. It can be retrieved using capturedValues() later. */ void ScriptBuilder::captureBlockReturnValue() { if (!impl->lastBlock) return; - auto valueBlock = impl->lastBlock; + auto valueBlock = takeBlock(); addBlock("script_builder_capture"); addObscuredInput("VALUE", valueBlock); } @@ -104,6 +98,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptrsetParent(impl->lastBlock); while (block) { block->setId(std::to_string(impl->blockId++)); @@ -112,7 +107,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptrparent(); auto next = block->next(); - if (parent) + if (parent && block != valueBlock) parent->setNext(block); if (next) @@ -174,7 +169,11 @@ void ScriptBuilder::addEntityInput(const std::string &name, const std::string &e if (!impl->lastBlock) return; - entity->setId(std::to_string(impl->blockId++)); + if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) { + entity->setId(std::to_string(impl->blockId++)); + impl->entities.push_back(entity); + } + auto input = std::make_shared(name, Input::Type::Shadow); input->setPrimaryValue(entityName); input->primaryValue()->setValuePtr(entity); @@ -188,7 +187,11 @@ void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptrlastBlock) return; - entity->setId(std::to_string(impl->blockId++)); + if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) { + entity->setId(std::to_string(impl->blockId++)); + impl->entities.push_back(entity); + } + auto field = std::make_shared(name, Value(), entity); impl->lastBlock->addField(field); } @@ -203,12 +206,41 @@ std::shared_ptr ScriptBuilder::currentBlock() if (!impl->lastBlock) return nullptr; - if (!impl->lastBlock->compileFunction()) - build(std::make_shared()); + if (!impl->lastBlock->compileFunction()) { + auto target = std::make_shared(); + const auto &variables = impl->target->variables(); + const auto &lists = impl->target->lists(); + + for (auto var : variables) + target->addVariable(var); + + for (auto list : lists) + target->addList(list); + + build(target); + } return impl->lastBlock; } +/*! Removes the current block from the script and returns it. Can be used in inputs later. */ +std::shared_ptr ScriptBuilder::takeBlock() +{ + if (!impl->lastBlock) + return nullptr; + + auto block = impl->lastBlock; + impl->blocks.pop_back(); + + if (!impl->blocks.empty()) + impl->blocks.back()->setNext(nullptr); + + block->setParent(nullptr); + block->setNext(nullptr); + + return block; +} + /*! Builds and compiles the script. */ void ScriptBuilder::build() { diff --git a/src/dev/test/scriptbuilder_p.h b/src/dev/test/scriptbuilder_p.h index 9b332b54..67688bd5 100644 --- a/src/dev/test/scriptbuilder_p.h +++ b/src/dev/test/scriptbuilder_p.h @@ -11,6 +11,7 @@ namespace libscratchcpp class IEngine; class Target; class Block; +class Entity; class List; } // namespace libscratchcpp @@ -29,6 +30,7 @@ class ScriptBuilderPrivate std::shared_ptr lastBlock; std::vector> blocks; std::vector> inputBlocks; + std::vector> entities; unsigned int blockId = 0; }; diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index 0da37ff3..d0c85537 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -192,7 +192,7 @@ extern "C" long value_toLong(const libscratchcpp::ValueData *v) { if (v->type == ValueType::Number) { - return v->numberValue; + return std::isnan(v->numberValue) || std::isinf(v->numberValue) ? 0 : v->numberValue; } else if (v->type == ValueType::Bool) return v->boolValue; else if (v->type == ValueType::String) @@ -205,7 +205,7 @@ extern "C" int value_toInt(const libscratchcpp::ValueData *v) { if (v->type == ValueType::Number) - return v->numberValue; + return std::isnan(v->numberValue) || std::isinf(v->numberValue) ? 0 : v->numberValue; else if (v->type == ValueType::Bool) return v->boolValue; else if (v->type == ValueType::String) diff --git a/test/dev/blocks/CMakeLists.txt b/test/dev/blocks/CMakeLists.txt index c36e43cf..5239c047 100644 --- a/test/dev/blocks/CMakeLists.txt +++ b/test/dev/blocks/CMakeLists.txt @@ -169,6 +169,7 @@ if (LIBSCRATCHCPP_ENABLE_LIST_BLOCKS) GTest::gmock_main scratchcpp scratchcpp_mocks + block_test_deps ) gtest_discover_tests(list_blocks_test) diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp index 8f98a2fb..bccc5bb1 100644 --- a/test/dev/blocks/list_blocks_test.cpp +++ b/test/dev/blocks/list_blocks_test.cpp @@ -1,15 +1,469 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include "../common.h" +#include "util.h" #include "dev/blocks/listblocks.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; + +using ::testing::Return; class ListBlocksTest : public testing::Test { public: - void SetUp() override { m_extension = std::make_unique(); } + void SetUp() override + { + m_extension = std::make_unique(); + m_engine = m_project.engine().get(); + m_extension->registerBlocks(m_engine); + 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(ListBlocksTest, AddToList) +{ + auto target = std::make_shared(); + auto list1 = std::make_shared("", ""); + target->addList(list1); + auto list2 = std::make_shared("", ""); + target->addList(list2); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("data_addtolist"); + builder.addValueInput("ITEM", "test"); + builder.addEntityField("LIST", list1); + + builder.addBlock("data_addtolist"); + builder.addValueInput("ITEM", true); + builder.addEntityField("LIST", list1); + + builder.addBlock("data_addtolist"); + builder.addValueInput("ITEM", 123); + builder.addEntityField("LIST", list2); + + builder.addBlock("data_addtolist"); + builder.addValueInput("ITEM", "Hello world"); + builder.addEntityField("LIST", list2); + + builder.build(); + + builder.run(); + ASSERT_EQ(list1->toString(), "test true"); + ASSERT_EQ(list2->toString(), "123 Hello world"); +} + +TEST_F(ListBlocksTest, DeleteOfList) +{ + auto target = std::make_shared(); + + auto list1 = std::make_shared("", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("", ""); + list2->append("Hello"); + list2->append("world"); + list2->append(false); + list2->append(-543.5); + list2->append("abc"); + list2->append(52.4); + target->addList(list2); + + auto list3 = std::make_shared("", ""); + list3->append(1); + list3->append(2); + list3->append(3); + target->addList(list3); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addTest = [&builder](const Value &index, std::shared_ptr list) { + builder.addBlock("data_deleteoflist"); + builder.addValueInput("INDEX", index); + builder.addEntityField("LIST", list); + return builder.currentBlock(); + }; + + auto block = addTest(1, list1); + addTest(3, list1); + addTest(2, list1); + addTest(0, list1); + addTest(3, list1); + + addTest("last", list2); + addTest("random", list2); + addTest("any", list2); + + addTest("all", list3); + + builder.build(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + ctx->setRng(&m_rng); + + EXPECT_CALL(m_rng, randint(1, 5)).WillOnce(Return(2)); + EXPECT_CALL(m_rng, randint(1, 4)).WillOnce(Return(3)); + code->run(ctx.get()); + ASSERT_EQ(list1->toString(), "ipsum true"); + ASSERT_EQ(list2->toString(), "Hello false abc"); + ASSERT_TRUE(list3->empty()); +} + +TEST_F(ListBlocksTest, DeleteAllOfList) +{ + auto target = std::make_shared(); + + auto list = std::make_shared("", ""); + list->append("Lorem"); + list->append("ipsum"); + list->append("dolor"); + list->append(123); + list->append(true); + target->addList(list); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("data_deletealloflist"); + builder.addEntityField("LIST", list); + builder.build(); + + builder.run(); + ASSERT_TRUE(list->empty()); +} + +TEST_F(ListBlocksTest, InsertAtList) +{ + auto target = std::make_shared(); + + auto list1 = std::make_shared("", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("", ""); + list2->append("Hello"); + list2->append("world"); + list2->append(false); + list2->append(-543.5); + list2->append("abc"); + list2->append(52.4); + target->addList(list2); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addTest = [&builder](const Value &index, const Value &item, std::shared_ptr list) { + builder.addBlock("data_insertatlist"); + builder.addValueInput("ITEM", item); + builder.addValueInput("INDEX", index); + builder.addEntityField("LIST", list); + return builder.currentBlock(); + }; + + auto block = addTest(4, "sit", list1); + addTest(7, false, list1); + addTest(0, "test", list1); + addTest(9, "test", list1); + + addTest("last", "lorem", list2); + addTest("random", "ipsum", list2); + addTest("any", "dolor", list2); + + builder.build(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + ctx->setRng(&m_rng); + + EXPECT_CALL(m_rng, randint(1, 8)).WillOnce(Return(8)); + EXPECT_CALL(m_rng, randint(1, 9)).WillOnce(Return(3)); + code->run(ctx.get()); + ASSERT_EQ(list1->toString(), "Lorem ipsum dolor sit 123 true false"); + ASSERT_EQ(list2->toString(), "Hello world dolor false -543.5 abc 52.4 lorem ipsum"); +} + +TEST_F(ListBlocksTest, ReplaceItemOfList) +{ + auto target = std::make_shared(); + + auto list1 = std::make_shared("", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("", ""); + list2->append("Hello"); + list2->append("world"); + list2->append(false); + list2->append(-543.5); + list2->append("abc"); + list2->append(52.4); + target->addList(list2); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addTest = [&builder](const Value &index, const Value &item, std::shared_ptr list) { + builder.addBlock("data_replaceitemoflist"); + builder.addValueInput("INDEX", index); + builder.addEntityField("LIST", list); + builder.addValueInput("ITEM", item); + return builder.currentBlock(); + }; + + auto block = addTest(4, "sit", list1); + addTest(5, -53.18, list1); + addTest(0, "test", list1); + addTest(6, "test", list1); + + addTest("last", "lorem", list2); + addTest("random", "ipsum", list2); + addTest("any", "dolor", list2); + + builder.build(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + ctx->setRng(&m_rng); + + EXPECT_CALL(m_rng, randint(1, 6)).WillOnce(Return(4)).WillOnce(Return(1)); + code->run(ctx.get()); + ASSERT_EQ(list1->toString(), "Lorem ipsum dolor sit -53.18"); + ASSERT_EQ(list2->toString(), "dolor world false ipsum abc lorem"); +} + +TEST_F(ListBlocksTest, ItemOfList) +{ + auto target = std::make_shared(); + + auto list = std::make_shared("list", ""); + list->append("Lorem"); + list->append("ipsum"); + list->append("dolor"); + list->append(123); + list->append(true); + target->addList(list); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addTest = [&builder](const Value &index, std::shared_ptr list) { + builder.addBlock("test_const_string"); + builder.addValueInput("STRING", index); + auto valueBlock = builder.takeBlock(); + + builder.addBlock("data_itemoflist"); + builder.addObscuredInput("INDEX", valueBlock); + builder.addEntityField("LIST", list); + auto block = builder.takeBlock(); + + builder.addBlock("test_print"); + builder.addObscuredInput("STRING", block); + return builder.currentBlock(); + }; + + auto block = addTest(3, list); + addTest(5, list); + addTest(0, list); + addTest(6, list); + + addTest("last", list); + addTest("random", list); + addTest("any", list); + + builder.build(); + + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block); + Script script(target.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + auto ctx = code->createExecutionContext(&thread); + ctx->setRng(&m_rng); + + static const std::string expected = + "dolor\n" + "true\n" + "0\n" + "0\n" + "true\n" + "123\n" + "Lorem\n"; + + EXPECT_CALL(m_rng, randint(1, 5)).WillOnce(Return(4)).WillOnce(Return(1)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true"); +} + +TEST_F(ListBlocksTest, ItemNumOfList) +{ + auto target = std::make_shared(); + + auto list = std::make_shared("list", ""); + list->append("Lorem"); + list->append("ipsum"); + list->append("dolor"); + list->append(123); + list->append(true); + list->append("dolor"); + target->addList(list); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addTest = [&builder](const Value &item, std::shared_ptr list) { + builder.addBlock("data_itemnumoflist"); + builder.addValueInput("ITEM", item); + builder.addEntityField("LIST", list); + auto block = builder.takeBlock(); + + builder.addBlock("test_print"); + builder.addObscuredInput("STRING", block); + return builder.currentBlock(); + }; + + auto block = addTest("dolor", list); + addTest(true, list); + addTest("nonexistent", list); + + builder.build(); + + static const std::string expected = + "3\n" + "5\n" + "0\n"; + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true dolor"); +} + +TEST_F(ListBlocksTest, LengthOfList) +{ + auto target = std::make_shared(); + + auto list1 = std::make_shared("list1", ""); + list1->append("Lorem"); + list1->append("ipsum"); + list1->append("dolor"); + list1->append(123); + list1->append(true); + target->addList(list1); + + auto list2 = std::make_shared("list2", ""); + list2->append(1); + list2->append(false); + target->addList(list2); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addTest = [&builder](std::shared_ptr list) { + builder.addBlock("data_lengthoflist"); + builder.addEntityField("LIST", list); + auto block = builder.takeBlock(); + + builder.addBlock("test_print"); + builder.addObscuredInput("STRING", block); + return builder.currentBlock(); + }; + + auto block = addTest(list1); + addTest(list2); + + builder.build(); + + static const std::string expected = + "5\n" + "2\n"; + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + ASSERT_EQ(list1->toString(), "Lorem ipsum dolor 123 true"); + ASSERT_EQ(list2->toString(), "1 false"); +} + +TEST_F(ListBlocksTest, ListContainsItem) +{ + auto target = std::make_shared(); + + auto list = std::make_shared("list", ""); + list->append("Lorem"); + list->append("ipsum"); + list->append("dolor"); + list->append(123); + list->append(true); + list->append("dolor"); + target->addList(list); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addTest = [&builder](const Value &item, std::shared_ptr list) { + builder.addBlock("data_listcontainsitem"); + builder.addEntityField("LIST", list); + builder.addValueInput("ITEM", item); + auto block = builder.takeBlock(); + + builder.addBlock("test_print"); + builder.addObscuredInput("STRING", block); + return builder.currentBlock(); + }; + + auto block = addTest("dolor", list); + addTest(true, list); + addTest("nonexistent", list); + + builder.build(); + + static const std::string expected = + "true\n" + "true\n" + "false\n"; + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + ASSERT_EQ(list->toString(), "Lorem ipsum dolor 123 true dolor"); +} diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp index 78345800..c81d9c7c 100644 --- a/test/dev/blocks/util.cpp +++ b/test/dev/blocks/util.cpp @@ -35,6 +35,11 @@ void registerBlocks(IEngine *engine, IExtension *extension) engine->addCompileFunction(extension, "test_input", [](Compiler *compiler) -> CompilerValue * { return compiler->addInput("INPUT"); }); + engine->addCompileFunction(extension, "test_const_string", [](Compiler *compiler) -> CompilerValue * { + auto input = compiler->addInput("STRING"); + return compiler->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { input }); + }); + engine->addCompileFunction(extension, "test_set_var", [](Compiler *compiler) -> CompilerValue * { Variable *var = static_cast(compiler->field("VARIABLE")->valuePtr().get()); compiler->createVariableWrite(var, compiler->addInput("VALUE")); @@ -52,4 +57,11 @@ extern "C" bool test_condition() return conditionReturnValue; } +extern "C" char *test_const_string(const char *str) +{ + char *ret = (char *)malloc((strlen(str) + 1) * sizeof(char)); + strcpy(ret, str); + return ret; +} + } // namespace libscratchcpp diff --git a/test/dev/compiler/CMakeLists.txt b/test/dev/compiler/CMakeLists.txt index feee72ca..cdc9696c 100644 --- a/test/dev/compiler/CMakeLists.txt +++ b/test/dev/compiler/CMakeLists.txt @@ -3,6 +3,7 @@ add_executable( compiler_test.cpp compilervalue_test.cpp compilerconstant_test.cpp + compilerlocalvariable_test.cpp ) target_link_libraries( diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp index c2abc8d2..945e2d0d 100644 --- a/test/dev/compiler/compiler_test.cpp +++ b/test/dev/compiler/compiler_test.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -180,6 +181,29 @@ TEST_F(CompilerTest, AddLoopIndex) compile(compiler, block); } +TEST_F(CompilerTest, AddLocalVariableValue) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Number); + CompilerValue ptr1(Compiler::StaticType::Number); + CompilerValue ptr2(Compiler::StaticType::Bool); + CompilerLocalVariable var1(&ptr1); + CompilerLocalVariable var2(&ptr2); + + EXPECT_CALL(*m_builder, addLocalVariableValue(&var1)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addLocalVariableValue(&var1), &ret); + + EXPECT_CALL(*m_builder, addLocalVariableValue(&var2)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addLocalVariableValue(&var2), nullptr); + + return nullptr; + }); + + compile(compiler, block); +} + TEST_F(CompilerTest, AddVariableValue) { Compiler compiler(&m_engine, &m_target); @@ -484,6 +508,24 @@ TEST_F(CompilerTest, CreateRandom) compile(compiler, block); } +TEST_F(CompilerTest, CreateRandomInt) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createRandomInt(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createRandomInt(&arg1, &arg2), &ret); + + return nullptr; + }); + + compile(compiler, block); +} + TEST_F(CompilerTest, CreateCmpEQ) { Compiler compiler(&m_engine, &m_target); @@ -887,6 +929,46 @@ TEST_F(CompilerTest, CreateSelect) compile(compiler, block); } +TEST_F(CompilerTest, CreateLocalVariable) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ptr1(Compiler::StaticType::Number); + CompilerLocalVariable var1(&ptr1); + EXPECT_CALL(*m_builder, createLocalVariable(var1.type())).WillOnce(Return(&var1)); + EXPECT_EQ(compiler->createLocalVariable(var1.type()), &var1); + + CompilerValue ptr2(Compiler::StaticType::Number); + CompilerLocalVariable var2(&ptr2); + EXPECT_CALL(*m_builder, createLocalVariable(var2.type())).WillOnce(Return(&var2)); + EXPECT_EQ(compiler->createLocalVariable(var2.type()), &var2); + + return nullptr; + }); + + compile(compiler, block); +} + +TEST_F(CompilerTest, CreateLocalVariableWrite) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ptr(Compiler::StaticType::Number); + CompilerLocalVariable var(&ptr); + CompilerValue arg(Compiler::StaticType::Number); + EXPECT_CALL(*m_builder, createLocalVariableWrite(&var, &arg)); + compiler->createLocalVariableWrite(&var, &arg); + + return nullptr; + }); + + compile(compiler, block); +} + TEST_F(CompilerTest, CreateVariableWrite) { Compiler compiler(&m_engine, &m_target); diff --git a/test/dev/compiler/compilerlocalvariable_test.cpp b/test/dev/compiler/compilerlocalvariable_test.cpp new file mode 100644 index 00000000..27cb0a25 --- /dev/null +++ b/test/dev/compiler/compilerlocalvariable_test.cpp @@ -0,0 +1,33 @@ +#include +#include +#include + +using namespace libscratchcpp; + +TEST(CompilerLocalVariableTest, Constructors) +{ + CompilerValue ptr(Compiler::StaticType::Number); + CompilerLocalVariable var(&ptr); + ASSERT_EQ(var.ptr(), &ptr); +} + +TEST(CompilerLocalVariableTest, Type) +{ + { + CompilerValue ptr(Compiler::StaticType::Number); + CompilerLocalVariable var(&ptr); + ASSERT_EQ(var.type(), ptr.type()); + } + + { + CompilerValue ptr(Compiler::StaticType::Bool); + CompilerLocalVariable var(&ptr); + ASSERT_EQ(var.type(), ptr.type()); + } + + { + CompilerValue ptr(Compiler::StaticType::String); + CompilerLocalVariable var(&ptr); + ASSERT_EQ(var.type(), ptr.type()); + } +} diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index fa631be4..9a7f2f1c 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -30,6 +30,7 @@ class LLVMCodeBuilderTest : public testing::Test Mul, Div, Random, + RandomInt, CmpEQ, CmpGT, CmpLT, @@ -98,6 +99,9 @@ class LLVMCodeBuilderTest : public testing::Test case OpType::Random: return m_builder->createRandom(arg1, arg2); + case OpType::RandomInt: + return m_builder->createRandomInt(arg1, arg2); + case OpType::CmpEQ: return m_builder->createCmpEQ(arg1, arg2); @@ -203,6 +207,9 @@ class LLVMCodeBuilderTest : public testing::Test return v1.isInt() && v2.isInt() ? m_rng.randint(v1.toLong(), v2.toLong()) : m_rng.randintDouble(v1.toDouble(), v2.toDouble()); } + case OpType::RandomInt: + return m_rng.randint(v1.toLong(), v2.toLong()); + case OpType::CmpEQ: return v1 == v2; @@ -698,6 +705,16 @@ TEST_F(LLVMCodeBuilderTest, Random) const double inf = std::numeric_limits::infinity(); const double nan = std::numeric_limits::quiet_NaN(); + + EXPECT_CALL(m_rng, randint(0, 5)).Times(3).WillRepeatedly(Return(5)); + runOpTest(OpType::Random, nan, 5); + + EXPECT_CALL(m_rng, randint(5, 0)).Times(3).WillRepeatedly(Return(3)); + runOpTest(OpType::Random, 5, nan); + + EXPECT_CALL(m_rng, randint(0, 0)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, nan, nan); + EXPECT_CALL(m_rng, randint).WillRepeatedly(Return(0)); EXPECT_CALL(m_rng, randintDouble).WillRepeatedly(Return(0)); @@ -717,6 +734,41 @@ TEST_F(LLVMCodeBuilderTest, Random) runOpTest(OpType::Random, -inf, inf, nan); } +TEST_F(LLVMCodeBuilderTest, RandomInt) +{ + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18)); + runOpTest(OpType::RandomInt, -45, 12); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5)); + runOpTest(OpType::RandomInt, -45.0, 12.0); + + EXPECT_CALL(m_rng, randint(12, 6)).Times(3).WillRepeatedly(Return(3)); + runOpTest(OpType::RandomInt, 12, 6.05); + + EXPECT_CALL(m_rng, randint(-78, -45)).Times(3).WillRepeatedly(Return(-59)); + runOpTest(OpType::RandomInt, -78.686, -45); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::RandomInt, "-45", "12"); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5)); + runOpTest(OpType::RandomInt, "-45.0", "12"); + + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-15)); + runOpTest(OpType::RandomInt, "-45", "12.0"); + + EXPECT_CALL(m_rng, randint(0, 1)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::RandomInt, false, true); + + EXPECT_CALL(m_rng, randint(1, 5)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::RandomInt, true, 5); + + EXPECT_CALL(m_rng, randint(8, 0)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::RandomInt, 8, false); + + // NOTE: Infinity, -Infinity and NaN behavior is undefined +} + TEST_F(LLVMCodeBuilderTest, EqualComparison) { runOpTest(OpType::CmpEQ, 10, 10); @@ -1467,6 +1519,63 @@ TEST_F(LLVMCodeBuilderTest, Exp10) runUnaryNumOpTest(OpType::Exp10, nan, 1.0); } +TEST_F(LLVMCodeBuilderTest, LocalVariables) +{ + EngineMock engine; + Stage stage; + Sprite sprite; + sprite.setEngine(&engine); + EXPECT_CALL(engine, stage()).WillRepeatedly(Return(&stage)); + + createBuilder(&sprite, true); + + CompilerLocalVariable *var1 = m_builder->createLocalVariable(Compiler::StaticType::Number); + CompilerLocalVariable *var2 = m_builder->createLocalVariable(Compiler::StaticType::Number); + CompilerLocalVariable *var3 = m_builder->createLocalVariable(Compiler::StaticType::Bool); + CompilerLocalVariable *var4 = m_builder->createLocalVariable(Compiler::StaticType::Bool); + + CompilerValue *v = m_builder->addConstValue(5); + m_builder->createLocalVariableWrite(var1, v); + + v = m_builder->addConstValue(-23.5); + v = callConstFuncForType(ValueType::Number, v); + m_builder->createLocalVariableWrite(var2, v); + + v = m_builder->addConstValue(5.2); + v = callConstFuncForType(ValueType::Number, v); + m_builder->createLocalVariableWrite(var2, v); + + v = m_builder->addConstValue(false); + m_builder->createLocalVariableWrite(var3, v); + + v = m_builder->addConstValue(true); + m_builder->createLocalVariableWrite(var3, v); + + v = m_builder->addConstValue(false); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->createLocalVariableWrite(var4, v); + + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var1) }); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var2) }); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var3) }); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { m_builder->addLocalVariableValue(var4) }); + + static const std::string expected = + "5\n" + "5.2\n" + "true\n" + "false\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + TEST_F(LLVMCodeBuilderTest, WriteVariable) { EngineMock engine; @@ -1585,6 +1694,15 @@ TEST_F(LLVMCodeBuilderTest, Select) v = m_builder->createSelect(v, m_builder->addConstValue(1), m_builder->addConstValue("false"), Compiler::StaticType::Bool); m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + // Unknown types + v = m_builder->addConstValue(true); + v = m_builder->createSelect(v, m_builder->addConstValue("test"), m_builder->addConstValue(-456.2), Compiler::StaticType::Unknown); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(false); + v = m_builder->createSelect(v, m_builder->addConstValue("abc"), m_builder->addConstValue(true), Compiler::StaticType::Unknown); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + static const std::string expected = "5.8\n" "-17.42\n" @@ -1595,7 +1713,9 @@ TEST_F(LLVMCodeBuilderTest, Select) "543\n" "0\n" "1\n" - "0\n"; + "0\n" + "test\n" + "true\n"; auto code = m_builder->finalize(); testing::internal::CaptureStdout(); @@ -2645,6 +2765,7 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) m_builder->endIf(); // Nested 1 + CompilerValue *str = m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { m_builder->addConstValue("test") }); v = m_builder->addConstValue(true); m_builder->beginIfStatement(v); { @@ -2659,6 +2780,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) v = m_builder->addConstValue(1); m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + // str should still be allocated + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str }); + v = m_builder->addConstValue(false); m_builder->beginIfStatement(v); m_builder->beginElseBranch(); @@ -2667,6 +2791,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); } m_builder->endIf(); + + // str should still be allocated + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str }); } m_builder->endIf(); } @@ -2687,6 +2814,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) } m_builder->endIf(); + // str should still be allocated + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str }); + // Nested 2 v = m_builder->addConstValue(false); m_builder->beginIfStatement(v); @@ -2706,6 +2836,8 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) } m_builder->beginElseBranch(); { + str = m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { m_builder->addConstValue("test") }); + v = m_builder->addConstValue(true); m_builder->beginIfStatement(v); { @@ -2714,6 +2846,9 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) } m_builder->beginElseBranch(); m_builder->endIf(); + + // str should still be allocated + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str }); } m_builder->endIf(); @@ -2735,8 +2870,12 @@ TEST_F(LLVMCodeBuilderTest, IfStatement) "no_args_ret\n" "1_arg 9\n" "1_arg 1\n" + "test\n" "1_arg 2\n" - "1_arg 7\n"; + "test\n" + "test\n" + "1_arg 7\n" + "test\n"; EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); testing::internal::CaptureStdout(); @@ -3024,6 +3163,7 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop) m_builder->endLoop(); // Nested + CompilerValue *str = m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { m_builder->addConstValue("test") }); v = m_builder->addConstValue(2); m_builder->beginRepeatLoop(v); { @@ -3032,6 +3172,9 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop) { v = m_builder->addConstValue(1); m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + // str should still be allocated + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str }); } m_builder->endLoop(); @@ -3048,6 +3191,9 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop) } m_builder->endLoop(); + // str should still be allocated + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { str }); + auto code = m_builder->finalize(); Script script(&m_target, nullptr, nullptr); script.setCode(code); @@ -3066,17 +3212,22 @@ TEST_F(LLVMCodeBuilderTest, RepeatLoop) "0\n" "1\n" "1_arg 1\n" + "test\n" "1_arg 1\n" + "test\n" "1_arg 2\n" "0\n" "1\n" "2\n" "1_arg 1\n" + "test\n" "1_arg 1\n" + "test\n" "1_arg 2\n" "0\n" "1\n" - "2\n"; + "2\n" + "test\n"; EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); testing::internal::CaptureStdout(); diff --git a/test/dev/test_api/scriptbuilder_test.cpp b/test/dev/test_api/scriptbuilder_test.cpp index 3191d63f..2239832a 100644 --- a/test/dev/test_api/scriptbuilder_test.cpp +++ b/test/dev/test_api/scriptbuilder_test.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include "../../common.h" #include "testextension.h" @@ -129,6 +129,28 @@ TEST_F(ScriptBuilderTest, AddObscuredInputMultipleBlocks) ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\ntest\ntest\n"); } +TEST_F(ScriptBuilderTest, AdvancedObscuredInput) +{ + for (int i = 1; i <= 3; i++) { + m_builder->addBlock("test_input"); + m_builder->addValueInput("INPUT", i); + auto valueBlock = m_builder->takeBlock(); + + m_builder->addBlock("test_input"); + m_builder->addObscuredInput("INPUT", valueBlock); + valueBlock = m_builder->takeBlock(); + + m_builder->addBlock("test_print"); + m_builder->addObscuredInput("STRING", valueBlock); + } + + m_builder->build(); + + testing::internal::CaptureStdout(); + m_builder->run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "1\n2\n3\n"); +} + TEST_F(ScriptBuilderTest, AddNullObscuredInput) { m_builder->addBlock("test_print"); @@ -183,45 +205,53 @@ TEST_F(ScriptBuilderTest, AddDropdownField) TEST_F(ScriptBuilderTest, AddEntityInput) { - auto broadcast = std::make_shared("", ""); - m_engine->setBroadcasts({ broadcast }); + auto var = std::make_shared("", ""); + m_target->addVariable(var); m_builder->addBlock("test_simple"); - m_builder->addEntityInput("BROADCAST", "test", InputValue::Type::Broadcast, broadcast); + m_builder->addEntityInput("VARIABLE", "test", InputValue::Type::Variable, var); auto block = m_builder->currentBlock(); ASSERT_TRUE(block); ASSERT_EQ(block->opcode(), "test_simple"); ASSERT_EQ(block->inputs().size(), 1); - ASSERT_EQ(block->inputAt(0)->name(), "BROADCAST"); - ASSERT_EQ(block->inputAt(0)->primaryValue()->valuePtr(), broadcast); - ASSERT_EQ(block->inputAt(0)->primaryValue()->type(), InputValue::Type::Broadcast); + ASSERT_EQ(block->inputAt(0)->name(), "VARIABLE"); + ASSERT_EQ(block->inputAt(0)->primaryValue()->valuePtr(), var); + ASSERT_EQ(block->inputAt(0)->primaryValue()->type(), InputValue::Type::Variable); + + m_builder->addBlock("test_simple"); + m_builder->addEntityInput("VARIABLE", "test", InputValue::Type::Variable, var); + m_builder->build(); } TEST_F(ScriptBuilderTest, AddEntityField) { - auto broadcast = std::make_shared("", ""); - m_engine->setBroadcasts({ broadcast }); + auto var = std::make_shared("", ""); + m_target->addVariable(var); m_builder->addBlock("test_simple"); - m_builder->addEntityField("BROADCAST", broadcast); + m_builder->addEntityField("VARIABLE", var); auto block = m_builder->currentBlock(); ASSERT_TRUE(block); ASSERT_EQ(block->opcode(), "test_simple"); ASSERT_TRUE(block->inputs().empty()); ASSERT_EQ(block->fields().size(), 1); - ASSERT_EQ(block->fieldAt(0)->name(), "BROADCAST"); - ASSERT_EQ(block->fieldAt(0)->valuePtr(), broadcast); + ASSERT_EQ(block->fieldAt(0)->name(), "VARIABLE"); + ASSERT_EQ(block->fieldAt(0)->valuePtr(), var); + + m_builder->addBlock("test_simple"); + m_builder->addEntityField("VARIABLE", var); + m_builder->build(); } -TEST_F(ScriptBuilderTest, ReporterBlocks) +TEST_F(ScriptBuilderTest, CaptureBlockReturnValue) { - m_builder->addReporterBlock("test_teststr"); + m_builder->addBlock("test_teststr"); auto block = m_builder->currentBlock(); ASSERT_TRUE(block); ASSERT_EQ(block->opcode(), "test_teststr"); m_builder->captureBlockReturnValue(); - m_builder->addReporterBlock("test_input"); + m_builder->addBlock("test_input"); m_builder->addValueInput("INPUT", -93.4); block = m_builder->currentBlock(); ASSERT_TRUE(block); diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h index bd381144..2862bea8 100644 --- a/test/mocks/codebuildermock.h +++ b/test/mocks/codebuildermock.h @@ -14,6 +14,7 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(CompilerValue *, addFunctionCallWithCtx, (const std::string &, Compiler::StaticType, const Compiler::ArgTypes &, const Compiler::Args &), (override)); MOCK_METHOD(CompilerConstant *, addConstValue, (const Value &), (override)); MOCK_METHOD(CompilerValue *, addLoopIndex, (), (override)); + MOCK_METHOD(CompilerValue *, addLocalVariableValue, (CompilerLocalVariable *), (override)); MOCK_METHOD(CompilerValue *, addVariableValue, (Variable *), (override)); MOCK_METHOD(CompilerValue *, addListContents, (List *), (override)); MOCK_METHOD(CompilerValue *, addListItem, (List *, CompilerValue *), (override)); @@ -27,6 +28,7 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(CompilerValue *, createDiv, (CompilerValue *, CompilerValue *), (override)); MOCK_METHOD(CompilerValue *, createRandom, (CompilerValue *, CompilerValue *), (override)); + MOCK_METHOD(CompilerValue *, createRandomInt, (CompilerValue *, CompilerValue *), (override)); MOCK_METHOD(CompilerValue *, createCmpEQ, (CompilerValue *, CompilerValue *), (override)); MOCK_METHOD(CompilerValue *, createCmpGT, (CompilerValue *, CompilerValue *), (override)); @@ -55,6 +57,9 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(CompilerValue *, createSelect, (CompilerValue *, CompilerValue *, CompilerValue *, Compiler::StaticType), (override)); + MOCK_METHOD(CompilerLocalVariable *, createLocalVariable, (Compiler::StaticType), (override)); + MOCK_METHOD(void, createLocalVariableWrite, (CompilerLocalVariable *, CompilerValue *), (override)); + MOCK_METHOD(void, createVariableWrite, (Variable *, CompilerValue *), (override)); MOCK_METHOD(void, createListClear, (List *), (override)); diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp index f59f3078..901d44ad 100644 --- a/test/scratch_classes/value_test.cpp +++ b/test/scratch_classes/value_test.cpp @@ -653,6 +653,13 @@ TEST(ValueTest, ToInt) v = "NaN"; ASSERT_EQ(v.toInt(), 0); + v = std::numeric_limits::infinity(); + ASSERT_EQ(v.toInt(), 0); + v = -std::numeric_limits::infinity(); + ASSERT_EQ(v.toInt(), 0); + v = std::numeric_limits::quiet_NaN(); + ASSERT_EQ(v.toInt(), 0); + v = "something"; ASSERT_EQ(v.toInt(), 0); @@ -795,6 +802,13 @@ TEST(ValueTest, ToLong) v = "NaN"; ASSERT_EQ(v.toLong(), 0); + v = std::numeric_limits::infinity(); + ASSERT_EQ(v.toLong(), 0); + v = -std::numeric_limits::infinity(); + ASSERT_EQ(v.toLong(), 0); + v = std::numeric_limits::quiet_NaN(); + ASSERT_EQ(v.toLong(), 0); + v = "something"; ASSERT_EQ(v.toLong(), 0);