diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h index 1ffc40db..aa094c46 100644 --- a/include/scratchcpp/dev/compiler.h +++ b/include/scratchcpp/dev/compiler.h @@ -40,9 +40,13 @@ class LIBSCRATCHCPP_EXPORT Compiler void moveToIf(std::shared_ptr substack); void moveToIfElse(std::shared_ptr substack1, std::shared_ptr substack2); - void moveToLoop(std::shared_ptr substack); + void moveToRepeatLoop(std::shared_ptr substack); + void moveToWhileLoop(std::shared_ptr substack); + void moveToRepeatUntilLoop(std::shared_ptr substack); + void beginLoopCondition(); void warp(); + Input *input(const std::string &name) const; Field *field(const std::string &name) const; const std::unordered_set &unsupportedBlocks() const; diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp index 7abb95d8..84376d1f 100644 --- a/src/dev/engine/compiler.cpp +++ b/src/dev/engine/compiler.cpp @@ -39,6 +39,8 @@ std::shared_ptr Compiler::block() const std::shared_ptr Compiler::compile(std::shared_ptr startBlock) { impl->builder = impl->builderFactory->create(startBlock->id()); + impl->substackTree.clear(); + impl->substackHit = false; impl->warp = false; impl->block = startBlock; @@ -52,10 +54,13 @@ std::shared_ptr Compiler::compile(std::shared_ptr startBl impl->unsupportedBlocks.insert(impl->block->opcode()); } - if (substacks != impl->substackTree.size()) + if (impl->substackHit) { + impl->substackHit = false; continue; + } - impl->block = impl->block->next(); + if (impl->block) + impl->block = impl->block->next(); if (!impl->block && !impl->substackTree.empty()) impl->substackEnd(); @@ -100,39 +105,84 @@ void Compiler::addInput(const std::string &name) /*! Jumps to the given if substack. */ void Compiler::moveToIf(std::shared_ptr substack) { + if (!substack) + return; // ignore empty if statements + + impl->substackHit = true; impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::IfStatement }); impl->block = substack; - - if (!impl->block) - impl->substackEnd(); + impl->builder->beginIfStatement(); } /*! Jumps to the given if/else substack. The second substack is used for the else branch. */ void Compiler::moveToIfElse(std::shared_ptr substack1, std::shared_ptr substack2) { + if (!substack1 && !substack2) + return; // ignore empty if statements + + impl->substackHit = true; impl->substackTree.push_back({ { impl->block, substack2 }, CompilerPrivate::SubstackType::IfStatement }); impl->block = substack1; + impl->builder->beginIfStatement(); if (!impl->block) impl->substackEnd(); } -/*! Jumps to the given loop substack. */ -void Compiler::moveToLoop(std::shared_ptr substack) +/*! Jumps to the given repeat loop substack. */ +void Compiler::moveToRepeatLoop(std::shared_ptr substack) { + impl->substackHit = true; impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); impl->block = substack; + impl->builder->beginRepeatLoop(); if (!impl->block) impl->substackEnd(); } +/*! Jumps to the given while loop substack. */ +void Compiler::moveToWhileLoop(std::shared_ptr substack) +{ + impl->substackHit = true; + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); + impl->block = substack; + impl->builder->beginWhileLoop(); + + if (!impl->block) + impl->substackEnd(); +} + +/*! Jumps to the given until loop substack. */ +void Compiler::moveToRepeatUntilLoop(std::shared_ptr substack) +{ + impl->substackHit = true; + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); + impl->block = substack; + impl->builder->beginRepeatUntilLoop(); + + if (!impl->block) + impl->substackEnd(); +} + +/*! Begins a while/until loop condition. */ +void Compiler::beginLoopCondition() +{ + impl->builder->beginLoopCondition(); +} + /*! Makes current script run without screen refresh. */ void Compiler::warp() { impl->warp = true; } +/*! Convenience method which returns the field with the given name. */ +Input *Compiler::input(const std::string &name) const +{ + return impl->block->inputAt(impl->block->findInput(name)).get(); +} + /*! Convenience method which returns the field with the given name. */ Field *Compiler::field(const std::string &name) const { diff --git a/src/dev/engine/compiler_p.cpp b/src/dev/engine/compiler_p.cpp index 629f56f4..cff14fe9 100644 --- a/src/dev/engine/compiler_p.cpp +++ b/src/dev/engine/compiler_p.cpp @@ -19,13 +19,13 @@ CompilerPrivate::CompilerPrivate(IEngine *engine, Target *target) : void CompilerPrivate::substackEnd() { - auto parent = substackTree.back(); + auto &parent = substackTree.back(); switch (parent.second) { case SubstackType::Loop: // Yield at loop end if not running without screen refresh - if (!warp) - builder->yield(); + /*if (!warp) + builder->yield();*/ builder->endLoop(); break; diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h index 3fda8f72..059d143b 100644 --- a/src/dev/engine/compiler_p.h +++ b/src/dev/engine/compiler_p.h @@ -32,6 +32,7 @@ struct CompilerPrivate std::shared_ptr block; std::vector, std::shared_ptr>, SubstackType>> substackTree; + bool substackHit = false; bool warp = false; static inline ICodeBuilderFactory *builderFactory = nullptr; diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h index c1ea3f0a..5a0ed8be 100644 --- a/src/dev/engine/internal/icodebuilder.h +++ b/src/dev/engine/internal/icodebuilder.h @@ -29,7 +29,10 @@ class ICodeBuilder virtual void beginElseBranch() = 0; virtual void endIf() = 0; - virtual void beginLoop() = 0; + virtual void beginRepeatLoop() = 0; + virtual void beginWhileLoop() = 0; + virtual void beginRepeatUntilLoop() = 0; + virtual void beginLoopCondition() = 0; virtual void endLoop() = 0; virtual void yield() = 0; diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp index 997865fb..943ff064 100644 --- a/src/dev/engine/internal/llvmcodebuilder.cpp +++ b/src/dev/engine/internal/llvmcodebuilder.cpp @@ -38,6 +38,8 @@ std::shared_ptr LLVMCodeBuilder::finalize() { size_t functionIndex = 0; llvm::Function *currentFunc = beginFunction(functionIndex); + std::vector ifStatements; + std::vector loops; // Execute recorded steps for (const Step &step : m_steps) { @@ -71,6 +73,185 @@ std::shared_ptr LLVMCodeBuilder::finalize() endFunction(currentFunc, functionIndex); currentFunc = beginFunction(++functionIndex); break; + + case Step::Type::BeginIf: { + IfStatement statement; + statement.beforeIf = m_builder.GetInsertBlock(); + statement.body = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + // Convert last reg to bool + assert(step.args.size() == 1); + statement.condition = m_builder.CreateCall(resolve_value_toBool(), step.args[0]->value); + + // Switch to body branch + m_builder.SetInsertPoint(statement.body); + + ifStatements.push_back(statement); + break; + } + + case Step::Type::BeginElse: { + assert(!ifStatements.empty()); + IfStatement &statement = ifStatements.back(); + + // Jump to the branch after the if statement + assert(!statement.afterIf); + statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + m_builder.CreateBr(statement.afterIf); + + // Create else branch + assert(!statement.elseBranch); + statement.elseBranch = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + // Since there's an else branch, the conditional instruction should jump to it + m_builder.SetInsertPoint(statement.beforeIf); + m_builder.CreateCondBr(statement.condition, statement.body, statement.elseBranch); + + // Switch to the else branch + m_builder.SetInsertPoint(statement.elseBranch); + break; + } + + case Step::Type::EndIf: { + assert(!ifStatements.empty()); + IfStatement &statement = ifStatements.back(); + + // Jump to the branch after the if statement + if (!statement.afterIf) + statement.afterIf = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + m_builder.CreateBr(statement.afterIf); + + if (statement.elseBranch) { + } else { + // If there wasn't an 'else' branch, create a conditional instruction which skips the if statement if false + m_builder.SetInsertPoint(statement.beforeIf); + m_builder.CreateCondBr(statement.condition, statement.body, statement.afterIf); + } + + // Switch to the branch after the if statement + m_builder.SetInsertPoint(statement.afterIf); + + ifStatements.pop_back(); + break; + } + + case Step::Type::BeginRepeatLoop: { + Loop loop; + loop.isRepeatLoop = true; + + // index = 0 + llvm::Constant *zero = llvm::ConstantInt::get(m_builder.getInt64Ty(), 0, true); + loop.index = m_builder.CreateAlloca(m_builder.getInt64Ty()); + m_builder.CreateStore(zero, loop.index); + + // Create branches + llvm::BasicBlock *roundBranch = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + // Convert last reg to double + assert(step.args.size() == 1); + llvm::Value *count = m_builder.CreateCall(resolve_value_toDouble(), step.args[0]->value); + + // 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))); + m_builder.CreateCondBr(comparison, loop.afterLoop, roundBranch); + + // Round (Scratch-specific behavior) + m_builder.SetInsertPoint(roundBranch); + llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::round, { count->getType() }); + count = m_builder.CreateCall(roundFunc, { count }); + count = m_builder.CreateFPToSI(count, m_builder.getInt64Ty()); // cast to signed integer + + // Jump to condition branch + m_builder.CreateBr(loop.conditionBranch); + + // Check index + m_builder.SetInsertPoint(loop.conditionBranch); + + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + if (!loop.afterLoop) + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index); + comparison = m_builder.CreateICmpULT(currentIndex, count); + m_builder.CreateCondBr(comparison, body, loop.afterLoop); + + // Switch to body branch + m_builder.SetInsertPoint(body); + + loops.push_back(loop); + break; + } + + case Step::Type::BeginWhileLoop: { + assert(!loops.empty()); + Loop &loop = loops.back(); + + // Create branches + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + // Convert last reg to bool and add condition + assert(step.args.size() == 1); + llvm::Value *condition = m_builder.CreateCall(resolve_value_toBool(), step.args[0]->value); + m_builder.CreateCondBr(condition, body, loop.afterLoop); + + // Switch to body branch + m_builder.SetInsertPoint(body); + break; + } + + case Step::Type::BeginRepeatUntilLoop: { + assert(!loops.empty()); + Loop &loop = loops.back(); + + // Create branches + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + + // Convert last reg to bool and add condition + assert(step.args.size() == 1); + llvm::Value *condition = m_builder.CreateCall(resolve_value_toBool(), step.args[0]->value); + m_builder.CreateCondBr(condition, loop.afterLoop, body); + + // Switch to body branch + m_builder.SetInsertPoint(body); + break; + } + + case Step::Type::BeginLoopCondition: { + Loop loop; + loop.isRepeatLoop = false; + loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", currentFunc); + m_builder.CreateBr(loop.conditionBranch); + m_builder.SetInsertPoint(loop.conditionBranch); + loops.push_back(loop); + break; + } + + case Step::Type::EndLoop: { + assert(!loops.empty()); + Loop &loop = loops.back(); + + if (loop.isRepeatLoop) { + // Increment index + llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index); + llvm::Value *incremented = m_builder.CreateAdd(currentIndex, llvm::ConstantInt::get(m_builder.getInt64Ty(), 1, true)); + m_builder.CreateStore(incremented, loop.index); + } + + // Jump to the condition branch + m_builder.CreateBr(loop.conditionBranch); + + // Switch to the branch after the loop + m_builder.SetInsertPoint(loop.afterLoop); + + loops.pop_back(); + break; + } } } @@ -140,22 +321,58 @@ void LLVMCodeBuilder::addListContents(List *list) void LLVMCodeBuilder::beginIfStatement() { + Step step(Step::Type::BeginIf); + assert(!m_tmpRegs.empty()); + step.args.push_back(m_tmpRegs.back()); + m_tmpRegs.pop_back(); + m_steps.push_back(step); } void LLVMCodeBuilder::beginElseBranch() { + m_steps.push_back(Step(Step::Type::BeginElse)); } void LLVMCodeBuilder::endIf() { + m_steps.push_back(Step(Step::Type::EndIf)); +} + +void LLVMCodeBuilder::beginRepeatLoop() +{ + Step step(Step::Type::BeginRepeatLoop); + assert(!m_tmpRegs.empty()); + step.args.push_back(m_tmpRegs.back()); + m_tmpRegs.pop_back(); + m_steps.push_back(step); +} + +void LLVMCodeBuilder::beginWhileLoop() +{ + Step step(Step::Type::BeginWhileLoop); + assert(!m_tmpRegs.empty()); + step.args.push_back(m_tmpRegs.back()); + m_tmpRegs.pop_back(); + m_steps.push_back(step); } -void LLVMCodeBuilder::beginLoop() +void LLVMCodeBuilder::beginRepeatUntilLoop() { + Step step(Step::Type::BeginRepeatUntilLoop); + assert(!m_tmpRegs.empty()); + step.args.push_back(m_tmpRegs.back()); + m_tmpRegs.pop_back(); + m_steps.push_back(step); +} + +void LLVMCodeBuilder::beginLoopCondition() +{ + m_steps.push_back(Step(Step::Type::BeginLoopCondition)); } void LLVMCodeBuilder::endLoop() { + m_steps.push_back(Step(Step::Type::EndLoop)); } void LLVMCodeBuilder::yield() @@ -289,3 +506,13 @@ llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_special() { return resolveFunction("value_assign_special", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt32Ty() }, false)); } + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toDouble() +{ + return resolveFunction("value_toDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toBool() +{ + return resolveFunction("value_toBool", llvm::FunctionType::get(m_builder.getInt1Ty(), m_valueDataType->getPointerTo(), false)); +} diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h index 3f0a3f55..530f244d 100644 --- a/src/dev/engine/internal/llvmcodebuilder.h +++ b/src/dev/engine/internal/llvmcodebuilder.h @@ -31,7 +31,10 @@ class LLVMCodeBuilder : public ICodeBuilder void beginElseBranch() override; void endIf() override; - void beginLoop() override; + void beginRepeatLoop() override; + void beginWhileLoop() override; + void beginRepeatUntilLoop() override; + void beginLoopCondition() override; void endLoop() override; void yield() override; @@ -49,7 +52,15 @@ class LLVMCodeBuilder : public ICodeBuilder enum class Type { FunctionCall, - Yield + Yield, + BeginIf, + BeginElse, + EndIf, + BeginRepeatLoop, + BeginWhileLoop, + BeginRepeatUntilLoop, + BeginLoopCondition, + EndLoop }; Step(Type type) : @@ -64,6 +75,23 @@ class LLVMCodeBuilder : public ICodeBuilder size_t functionReturnRegIndex = 0; }; + struct IfStatement + { + llvm::Value *condition = nullptr; + llvm::BasicBlock *beforeIf = nullptr; + llvm::BasicBlock *body = nullptr; + llvm::BasicBlock *elseBranch = nullptr; + llvm::BasicBlock *afterIf = nullptr; + }; + + struct Loop + { + bool isRepeatLoop = false; + llvm::Value *index = nullptr; + llvm::BasicBlock *conditionBranch = nullptr; + llvm::BasicBlock *afterLoop = nullptr; + }; + void initTypes(); llvm::Function *beginFunction(size_t index); void endFunction(llvm::Function *func, size_t index); @@ -76,6 +104,8 @@ class LLVMCodeBuilder : public ICodeBuilder llvm::FunctionCallee resolve_value_assign_bool(); llvm::FunctionCallee resolve_value_assign_cstring(); llvm::FunctionCallee resolve_value_assign_special(); + llvm::FunctionCallee resolve_value_toDouble(); + llvm::FunctionCallee resolve_value_toBool(); std::string m_id; llvm::LLVMContext m_ctx; diff --git a/test/dev/compiler/compiler_test.cpp b/test/dev/compiler/compiler_test.cpp index f103bd86..ba615499 100644 --- a/test/dev/compiler/compiler_test.cpp +++ b/test/dev/compiler/compiler_test.cpp @@ -216,6 +216,450 @@ TEST_F(CompilerTest, AddInput) compile(compiler, block); } +TEST_F(CompilerTest, MoveToIf) +{ + Compiler compiler(&m_engine, &m_target); + EXPECT_CALL(*m_builder, beginElseBranch).Times(0); + + auto if1 = std::make_shared("", "if"); + if1->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement).Times(0); + EXPECT_CALL(*m_builder, endIf).Times(0); + compiler->moveToIf(nullptr); + }); + + auto if2 = std::make_shared("", "if"); + if1->setNext(if2); + if2->setParent(if1); + if2->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement()); + EXPECT_CALL(*m_builder, addConstValue(Value())); + EXPECT_CALL(*m_builder, endIf()); + compiler->moveToIf(compiler->input("SUBSTACK")->valueBlock()); + }); + + auto substack1 = std::make_shared("", "substack"); + substack1->setParent(if2); + substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(Value()); }); + + auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack1); + if2->addInput(input); + + compile(compiler, if1); +} + +TEST_F(CompilerTest, MoveToIfElse) +{ + Compiler compiler(&m_engine, &m_target); + + auto if1 = std::make_shared("", "if"); + if1->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement).Times(0); + EXPECT_CALL(*m_builder, beginElseBranch).Times(0); + EXPECT_CALL(*m_builder, endIf).Times(0); + compiler->moveToIfElse(nullptr, nullptr); + }); + + auto if2 = std::make_shared("", "if"); + if1->setNext(if2); + if2->setParent(if1); + if2->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement()); + EXPECT_CALL(*m_builder, addConstValue(Value(1))); + EXPECT_CALL(*m_builder, beginElseBranch()); + EXPECT_CALL(*m_builder, addConstValue(Value(2))); + EXPECT_CALL(*m_builder, endIf()); + compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); + }); + + auto substack1 = std::make_shared("", "substack"); + substack1->setParent(if2); + substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + + auto substack2 = std::make_shared("", "substack"); + substack2->setParent(if2); + substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + + auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack1); + if2->addInput(input); + input = std::make_shared("SUBSTACK2", Input::Type::NoShadow); + input->setValueBlock(substack2); + if2->addInput(input); + + // Nested + auto if3 = std::make_shared("", "if"); + if2->setNext(if3); + if3->setParent(if2); + if3->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement()).Times(3); + EXPECT_CALL(*m_builder, beginElseBranch()).Times(3); + EXPECT_CALL(*m_builder, endIf()).Times(3); + EXPECT_CALL(*m_builder, addConstValue(Value(1))); + EXPECT_CALL(*m_builder, addConstValue(Value(2))); + EXPECT_CALL(*m_builder, addConstValue(Value(3))); + EXPECT_CALL(*m_builder, addConstValue(Value(4))); + compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); + }); + + // if + auto ifSubstack1 = std::make_shared("", "if"); + ifSubstack1->setParent(if3); + ifSubstack1->setCompileFunction([](Compiler *compiler) { compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); }); + + substack1 = std::make_shared("", "substack"); + substack1->setParent(ifSubstack1); + substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + + substack2 = std::make_shared("", "substack"); + substack2->setParent(ifSubstack1); + substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack1); + ifSubstack1->addInput(input); + input = std::make_shared("SUBSTACK2", Input::Type::NoShadow); + input->setValueBlock(substack2); + ifSubstack1->addInput(input); + + // else + auto ifSubstack2 = std::make_shared("", "if"); + ifSubstack2->setParent(if3); + ifSubstack2->setCompileFunction([](Compiler *compiler) { compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); }); + + substack1 = std::make_shared("", "substack"); + substack1->setParent(ifSubstack2); + substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(3); }); + + substack2 = std::make_shared("", "substack"); + substack2->setParent(ifSubstack2); + substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(4); }); + + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack1); + ifSubstack2->addInput(input); + input = std::make_shared("SUBSTACK2", Input::Type::NoShadow); + input->setValueBlock(substack2); + ifSubstack2->addInput(input); + + // End if + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(ifSubstack1); + if3->addInput(input); + input = std::make_shared("SUBSTACK2", Input::Type::NoShadow); + input->setValueBlock(ifSubstack2); + if3->addInput(input); + + // Empty 'then' branch + auto if4 = std::make_shared("", "if"); + if3->setNext(if4); + if4->setParent(if3); + if4->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement()); + EXPECT_CALL(*m_builder, beginElseBranch()); + EXPECT_CALL(*m_builder, addConstValue(Value(2))); + EXPECT_CALL(*m_builder, endIf()); + compiler->moveToIfElse(nullptr, compiler->input("SUBSTACK2")->valueBlock()); + }); + + substack2 = std::make_shared("", "substack"); + substack2->setParent(if4); + substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + + input = std::make_shared("SUBSTACK2", Input::Type::NoShadow); + input->setValueBlock(substack2); + if4->addInput(input); + + // Empty 'else' branch + auto if5 = std::make_shared("", "if"); + if4->setNext(if5); + if5->setParent(if4); + if5->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginIfStatement()); + EXPECT_CALL(*m_builder, addConstValue(Value(1))); + EXPECT_CALL(*m_builder, beginElseBranch()).Times(0); + EXPECT_CALL(*m_builder, endIf()); + compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), nullptr); + }); + + substack1 = std::make_shared("", "substack"); + substack1->setParent(if5); + substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack1); + if5->addInput(input); + + // Code after the if statement + auto block = std::make_shared("", ""); + block->setParent(if5); + if5->setNext(block); + block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + + EXPECT_CALL(*m_builder, addConstValue(Value("after"))); + compile(compiler, if1); +} + +TEST_F(CompilerTest, MoveToRepeatLoop) +{ + Compiler compiler(&m_engine, &m_target); + + auto l1 = std::make_shared("", "loop"); + l1->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatLoop()); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToRepeatLoop(nullptr); + }); + + auto l2 = std::make_shared("", "loop"); + l1->setNext(l2); + l2->setParent(l1); + l2->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatLoop()); + EXPECT_CALL(*m_builder, addConstValue(Value(2))); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock()); + }); + + auto substack = std::make_shared("", "substack"); + substack->setParent(l2); + substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + + auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack); + l2->addInput(input); + + // Nested + auto l3 = std::make_shared("", "loop"); + l2->setNext(l3); + l3->setParent(l2); + l3->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatLoop()).Times(2); + EXPECT_CALL(*m_builder, endLoop()).Times(2); + EXPECT_CALL(*m_builder, addConstValue(Value(1))); + compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock()); + }); + + // Begin loop + auto loopSubstack = std::make_shared("", "loop"); + loopSubstack->setParent(l3); + loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock()); }); + + substack = std::make_shared("", "substack"); + substack->setParent(loopSubstack); + substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack); + loopSubstack->addInput(input); + + // End loop + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(loopSubstack); + l3->addInput(input); + + // Empty loop body + auto l4 = std::make_shared("", "loop"); + l3->setNext(l4); + l4->setParent(l3); + l4->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatLoop()); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToRepeatLoop(nullptr); + }); + + // Code after the loop + auto block = std::make_shared("", ""); + block->setParent(l4); + l4->setNext(block); + block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + + EXPECT_CALL(*m_builder, addConstValue(Value("after"))); + compile(compiler, l1); +} + +TEST_F(CompilerTest, MoveToWhileLoop) +{ + Compiler compiler(&m_engine, &m_target); + + auto l1 = std::make_shared("", "loop"); + l1->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginWhileLoop()); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToWhileLoop(nullptr); + }); + + auto l2 = std::make_shared("", "loop"); + l1->setNext(l2); + l2->setParent(l1); + l2->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginWhileLoop()); + EXPECT_CALL(*m_builder, addConstValue(Value(2))); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock()); + }); + + auto substack = std::make_shared("", "substack"); + substack->setParent(l2); + substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + + auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack); + l2->addInput(input); + + // Nested + auto l3 = std::make_shared("", "loop"); + l2->setNext(l3); + l3->setParent(l2); + l3->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginWhileLoop()).Times(2); + EXPECT_CALL(*m_builder, endLoop()).Times(2); + EXPECT_CALL(*m_builder, addConstValue(Value(1))); + compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock()); + }); + + // Begin loop + auto loopSubstack = std::make_shared("", "loop"); + loopSubstack->setParent(l3); + loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock()); }); + + substack = std::make_shared("", "substack"); + substack->setParent(loopSubstack); + substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack); + loopSubstack->addInput(input); + + // End loop + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(loopSubstack); + l3->addInput(input); + + // Empty loop body + auto l4 = std::make_shared("", "loop"); + l3->setNext(l4); + l4->setParent(l3); + l4->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginWhileLoop()); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToWhileLoop(nullptr); + }); + + // Code after the loop + auto block = std::make_shared("", ""); + block->setParent(l4); + l4->setNext(block); + block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + + EXPECT_CALL(*m_builder, addConstValue(Value("after"))); + compile(compiler, l1); +} + +TEST_F(CompilerTest, MoveToRepeatUntilLoop) +{ + Compiler compiler(&m_engine, &m_target); + + auto l1 = std::make_shared("", "loop"); + l1->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatUntilLoop()); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToRepeatUntilLoop(nullptr); + }); + + auto l2 = std::make_shared("", "loop"); + l1->setNext(l2); + l2->setParent(l1); + l2->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatUntilLoop()); + EXPECT_CALL(*m_builder, addConstValue(Value(2))); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock()); + }); + + auto substack = std::make_shared("", "substack"); + substack->setParent(l2); + substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + + auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack); + l2->addInput(input); + + // Nested + auto l3 = std::make_shared("", "loop"); + l2->setNext(l3); + l3->setParent(l2); + l3->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatUntilLoop()).Times(2); + EXPECT_CALL(*m_builder, endLoop()).Times(2); + EXPECT_CALL(*m_builder, addConstValue(Value(1))); + compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock()); + }); + + // Begin loop + auto loopSubstack = std::make_shared("", "loop"); + loopSubstack->setParent(l3); + loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock()); }); + + substack = std::make_shared("", "substack"); + substack->setParent(loopSubstack); + substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(substack); + loopSubstack->addInput(input); + + // End loop + input = std::make_shared("SUBSTACK", Input::Type::NoShadow); + input->setValueBlock(loopSubstack); + l3->addInput(input); + + // Empty loop body + auto l4 = std::make_shared("", "loop"); + l3->setNext(l4); + l4->setParent(l3); + l4->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginRepeatUntilLoop()); + EXPECT_CALL(*m_builder, endLoop()); + compiler->moveToRepeatUntilLoop(nullptr); + }); + + // Code after the loop + auto block = std::make_shared("", ""); + block->setParent(l4); + l4->setNext(block); + block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + + EXPECT_CALL(*m_builder, addConstValue(Value("after"))); + compile(compiler, l1); +} + +TEST_F(CompilerTest, BeginLoopCondition) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) { + EXPECT_CALL(*m_builder, beginLoopCondition()); + compiler->beginLoopCondition(); + }); + + compile(compiler, block); +} + +TEST_F(CompilerTest, Input) +{ + Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); + block->addInput(std::make_shared("TEST", Input::Type::Shadow)); + block->setCompileFunction([](Compiler *compiler) { + ASSERT_EQ(compiler->input("INVALID"), nullptr); + ASSERT_EQ(compiler->input("TEST"), compiler->block()->inputAt(0).get()); + }); + + compile(compiler, block); +} + TEST_F(CompilerTest, Field) { Compiler compiler(&m_engine, &m_target); diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index ea3e20ac..18942ea0 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -8,6 +8,8 @@ using namespace libscratchcpp; +using ::testing::Return; + class LLVMCodeBuilderTest : public testing::Test { public: @@ -103,3 +105,415 @@ TEST_F(LLVMCodeBuilderTest, Yield) ASSERT_EQ(testing::internal::GetCapturedStdout(), expected2); ASSERT_TRUE(code->isFinished(ctx.get())); } + +TEST_F(LLVMCodeBuilderTest, IfStatement) +{ + // Without else branch (const condition) + m_builder->addConstValue("true"); + m_builder->beginIfStatement(); + m_builder->addFunctionCall("test_function_no_args", 0, false); + m_builder->endIf(); + + m_builder->addConstValue("false"); + m_builder->beginIfStatement(); + m_builder->addFunctionCall("test_function_no_args", 0, false); + m_builder->endIf(); + + // Without else branch (condition returned by function) + m_builder->addFunctionCall("test_function_no_args_ret", 0, true); + m_builder->addConstValue("no_args_output"); + m_builder->addFunctionCall("test_equals", 2, true); + m_builder->beginIfStatement(); + m_builder->addConstValue(0); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->endIf(); + + m_builder->addFunctionCall("test_function_no_args_ret", 0, true); + m_builder->addConstValue(""); + m_builder->addFunctionCall("test_equals", 2, true); + m_builder->beginIfStatement(); + m_builder->addConstValue(1); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->endIf(); + + // With else branch (const condition) + m_builder->addConstValue("true"); + m_builder->beginIfStatement(); + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->beginElseBranch(); + m_builder->addConstValue(3); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->endIf(); + + m_builder->addConstValue("false"); + m_builder->beginIfStatement(); + m_builder->addConstValue(4); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->beginElseBranch(); + m_builder->addConstValue(5); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->endIf(); + + // With else branch (condition returned by function) + m_builder->addFunctionCall("test_function_no_args_ret", 0, true); + m_builder->addConstValue("no_args_output"); + m_builder->addFunctionCall("test_equals", 2, true); + m_builder->beginIfStatement(); + m_builder->addConstValue(6); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->beginElseBranch(); + m_builder->addConstValue(7); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->endIf(); + + m_builder->addFunctionCall("test_function_no_args_ret", 0, true); + m_builder->addConstValue(""); + m_builder->addFunctionCall("test_equals", 2, true); + m_builder->beginIfStatement(); + m_builder->addConstValue(8); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->beginElseBranch(); + m_builder->addConstValue(9); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->endIf(); + + // Nested 1 + m_builder->addConstValue(true); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(false); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(0); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->beginElseBranch(); + { + m_builder->addConstValue(1); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + + m_builder->addConstValue(false); + m_builder->beginIfStatement(); + m_builder->beginElseBranch(); + { + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->endIf(); + } + m_builder->endIf(); + } + m_builder->beginElseBranch(); + { + m_builder->addConstValue(true); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(3); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->beginElseBranch(); + { + m_builder->addConstValue(4); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->endIf(); + } + m_builder->endIf(); + + // Nested 2 + m_builder->addConstValue(false); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(false); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(5); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->beginElseBranch(); + { + m_builder->addConstValue(6); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->endIf(); + } + m_builder->beginElseBranch(); + { + m_builder->addConstValue(true); + m_builder->beginIfStatement(); + { + m_builder->addConstValue(7); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->beginElseBranch(); + m_builder->endIf(); + } + m_builder->endIf(); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&m_target); + + static const std::string expected = + "no_args\n" + "no_args_ret\n" + "1_arg 0\n" + "no_args_ret\n" + "1_arg 2\n" + "1_arg 5\n" + "no_args_ret\n" + "1_arg 6\n" + "no_args_ret\n" + "1_arg 9\n" + "1_arg 1\n" + "1_arg 2\n" + "1_arg 7\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, RepeatLoop) +{ + // Const count + m_builder->addConstValue("-5"); + m_builder->beginRepeatLoop(); + m_builder->addFunctionCall("test_function_no_args", 0, false); + m_builder->endLoop(); + + m_builder->addConstValue(0); + m_builder->beginRepeatLoop(); + m_builder->addFunctionCall("test_function_no_args", 0, false); + m_builder->endLoop(); + + m_builder->addConstValue(3); + m_builder->beginRepeatLoop(); + m_builder->addFunctionCall("test_function_no_args", 0, false); + m_builder->endLoop(); + + m_builder->addConstValue("2"); + m_builder->beginRepeatLoop(); + m_builder->addConstValue(0); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->endLoop(); + + // Count returned by function + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_const", 1, true); + m_builder->beginRepeatLoop(); + m_builder->addFunctionCall("test_function_no_args", 0, false); + m_builder->endLoop(); + + // Nested + m_builder->addConstValue(2); + m_builder->beginRepeatLoop(); + { + m_builder->addConstValue(2); + m_builder->beginRepeatLoop(); + { + m_builder->addConstValue(1); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->endLoop(); + + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + + m_builder->addConstValue(3); + m_builder->beginRepeatLoop(); + { + m_builder->addConstValue(3); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&m_target); + + static const std::string expected = + "no_args\n" + "no_args\n" + "no_args\n" + "1_arg 0\n" + "1_arg 0\n" + "no_args\n" + "no_args\n" + "1_arg 1\n" + "1_arg 1\n" + "1_arg 2\n" + "1_arg 3\n" + "1_arg 3\n" + "1_arg 3\n" + "1_arg 1\n" + "1_arg 1\n" + "1_arg 2\n" + "1_arg 3\n" + "1_arg 3\n" + "1_arg 3\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, WhileLoop) +{ + // Const condition + m_builder->beginLoopCondition(); + m_builder->addConstValue("false"); + m_builder->beginWhileLoop(); + m_builder->addFunctionCall("test_unreachable", 0, false); + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + m_builder->addConstValue(false); + m_builder->beginWhileLoop(); + m_builder->addFunctionCall("test_unreachable", 0, false); + m_builder->endLoop(); + + // Condition returned by function + m_builder->addFunctionCall("test_reset_counter", 0, false); + m_builder->beginLoopCondition(); + m_builder->addFunctionCall("test_get_counter", 0, true); + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_lower_than", 2, true); + m_builder->beginWhileLoop(); + m_builder->addConstValue(0); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->addFunctionCall("test_increment_counter", 0, false); + m_builder->endLoop(); + + // Nested + m_builder->addFunctionCall("test_reset_counter", 0, false); + m_builder->beginLoopCondition(); + m_builder->addFunctionCall("test_get_counter", 0, true); + m_builder->addConstValue(3); + m_builder->addFunctionCall("test_lower_than", 2, true); + m_builder->beginWhileLoop(); + { + m_builder->beginLoopCondition(); + m_builder->addFunctionCall("test_get_counter", 0, true); + m_builder->addConstValue(3); + m_builder->addFunctionCall("test_lower_than", 2, true); + m_builder->beginWhileLoop(); + { + m_builder->addConstValue(1); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->addFunctionCall("test_increment_counter", 0, false); + } + m_builder->endLoop(); + + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + + m_builder->beginLoopCondition(); + m_builder->addConstValue(false); + m_builder->beginWhileLoop(); + { + m_builder->addFunctionCall("test_unreachable", 0, false); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&m_target); + + static const std::string expected = + "1_arg 0\n" + "1_arg 0\n" + "1_arg 1\n" + "1_arg 1\n" + "1_arg 1\n" + "1_arg 2\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop) +{ + // Const condition + m_builder->beginLoopCondition(); + m_builder->addConstValue("true"); + m_builder->beginRepeatUntilLoop(); + m_builder->addFunctionCall("test_unreachable", 0, false); + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(); + m_builder->addFunctionCall("test_unreachable", 0, false); + m_builder->endLoop(); + + // Condition returned by function + m_builder->addFunctionCall("test_reset_counter", 0, false); + m_builder->beginLoopCondition(); + m_builder->addFunctionCall("test_get_counter", 0, true); + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_lower_than", 2, true); + m_builder->addFunctionCall("test_not", 1, true); + m_builder->beginRepeatUntilLoop(); + m_builder->addConstValue(0); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->addFunctionCall("test_increment_counter", 0, false); + m_builder->endLoop(); + + // Nested + m_builder->addFunctionCall("test_reset_counter", 0, false); + m_builder->beginLoopCondition(); + m_builder->addFunctionCall("test_get_counter", 0, true); + m_builder->addConstValue(3); + m_builder->addFunctionCall("test_lower_than", 2, true); + m_builder->addFunctionCall("test_not", 1, true); + m_builder->beginRepeatUntilLoop(); + { + m_builder->beginLoopCondition(); + m_builder->addFunctionCall("test_get_counter", 0, true); + m_builder->addConstValue(3); + m_builder->addFunctionCall("test_lower_than", 2, true); + m_builder->addFunctionCall("test_not", 1, true); + m_builder->beginRepeatUntilLoop(); + { + m_builder->addConstValue(1); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + m_builder->addFunctionCall("test_increment_counter", 0, false); + } + m_builder->endLoop(); + + m_builder->addConstValue(2); + m_builder->addFunctionCall("test_function_1_arg", 1, false); + + m_builder->beginLoopCondition(); + m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(); + { + m_builder->addFunctionCall("test_unreachable", 0, false); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + auto code = m_builder->finalize(); + auto ctx = code->createExecutionContext(&m_target); + + static const std::string expected = + "1_arg 0\n" + "1_arg 0\n" + "1_arg 1\n" + "1_arg 1\n" + "1_arg 1\n" + "1_arg 2\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} diff --git a/test/dev/llvm/testfunctions.cpp b/test/dev/llvm/testfunctions.cpp index 7de1c780..c31d148f 100644 --- a/test/dev/llvm/testfunctions.cpp +++ b/test/dev/llvm/testfunctions.cpp @@ -7,6 +7,8 @@ using namespace libscratchcpp; +static int counter = 0; + extern "C" { void test_function(TestMock *mock, Target *target) @@ -73,4 +75,45 @@ extern "C" std::cout << "3_args " << s1 << " " << s2 << " " << s3 << std::endl; value_assign_cstring(ret, "3_args_output"); } + + void test_equals(Target *target, ValueData *ret, ValueData *a, ValueData *b) + { + value_assign_bool(ret, value_equals(a, b)); + } + + void test_unreachable(Target *target) + { + std::cout << "error: unreachable reached" << std::endl; + exit(1); + } + + void test_lower_than(Target *target, ValueData *ret, ValueData *a, ValueData *b) + { + value_assign_bool(ret, value_lower(a, b)); + } + + void test_const(Target *target, ValueData *ret, ValueData *v) + { + value_assign_copy(ret, v); + } + + void test_not(Target *target, ValueData *ret, ValueData *arg) + { + value_assign_bool(ret, !value_toBool(arg)); + } + + void test_reset_counter(Target *target) + { + counter = 0; + } + + void test_increment_counter(Target *target) + { + counter++; + } + + void test_get_counter(Target *target, ValueData *ret) + { + value_assign_int(ret, counter); + } } diff --git a/test/dev/llvm/testfunctions.h b/test/dev/llvm/testfunctions.h index 96acefa9..b0142013 100644 --- a/test/dev/llvm/testfunctions.h +++ b/test/dev/llvm/testfunctions.h @@ -18,6 +18,17 @@ extern "C" void test_function_1_arg_ret(Target *target, ValueData *ret, const ValueData *arg1); void test_function_3_args(Target *target, const ValueData *arg1, const ValueData *arg2, const ValueData *arg3); void test_function_3_args_ret(Target *target, ValueData *ret, const ValueData *arg1, const ValueData *arg2, const ValueData *arg3); + + void test_equals(Target *target, ValueData *ret, ValueData *a, ValueData *b); + void test_lower_than(Target *target, ValueData *ret, ValueData *a, ValueData *b); + void test_not(Target *target, ValueData *ret, ValueData *arg); + void test_const(Target *target, ValueData *ret, ValueData *v); + + void test_unreachable(Target *target); + + void test_reset_counter(Target *target); + void test_increment_counter(Target *target); + void test_get_counter(Target *target, ValueData *ret); } } // namespace libscratchcpp diff --git a/test/mocks/codebuildermock.h b/test/mocks/codebuildermock.h index 018a1b6f..ac9ca92d 100644 --- a/test/mocks/codebuildermock.h +++ b/test/mocks/codebuildermock.h @@ -18,7 +18,10 @@ class CodeBuilderMock : public ICodeBuilder MOCK_METHOD(void, beginElseBranch, (), (override)); MOCK_METHOD(void, endIf, (), (override)); - MOCK_METHOD(void, beginLoop, (), (override)); + MOCK_METHOD(void, beginRepeatLoop, (), (override)); + MOCK_METHOD(void, beginWhileLoop, (), (override)); + MOCK_METHOD(void, beginRepeatUntilLoop, (), (override)); + MOCK_METHOD(void, beginLoopCondition, (), (override)); MOCK_METHOD(void, endLoop, (), (override)); MOCK_METHOD(void, yield, (), (override));