Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
include/scratchcpp/dev/executablecode.h
include/scratchcpp/dev/executioncontext.h
include/scratchcpp/dev/promise.h
include/scratchcpp/dev/test/scriptbuilder.h
)

if(LIBSCRATCHCPP_PRINT_LLVM_IR)
Expand Down
58 changes: 58 additions & 0 deletions include/scratchcpp/dev/test/scriptbuilder.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <vector>

#include "../../global.h"
#include "../../spimpl.h"

namespace libscratchcpp
{

class IExtension;
class IEngine;
class Target;
class List;

} // namespace libscratchcpp

namespace libscratchcpp::test
{

class ScriptBuilderPrivate;

/*! \brief The ScriptBuilder class is used to build Scratch scripts in unit tests. */
class LIBSCRATCHCPP_EXPORT ScriptBuilder
{
public:
ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target);
ScriptBuilder(const ScriptBuilder &) = delete;

~ScriptBuilder();

void addBlock(const std::string &opcode);
void addReporterBlock(const std::string &opcode);
void captureBlockReturnValue();

void addValueInput(const std::string &name, const Value &value);
void addNullInput(const std::string &name);

void addObscuredInput(const std::string &name, std::shared_ptr<Block> valueBlock);
void addNullObscuredInput(const std::string &name);

void addDropdownInput(const std::string &name, const std::string &selectedValue);
void addDropdownField(const std::string &name, const std::string &selectedValue);

void build();
void run();

List *capturedValues() const;

private:
void addBlock(std::shared_ptr<Block> block);

spimpl::unique_impl_ptr<ScriptBuilderPrivate> impl;
};

} // namespace libscratchcpp::test
1 change: 1 addition & 0 deletions src/dev/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
add_subdirectory(blocks)
add_subdirectory(engine)
add_subdirectory(test)
6 changes: 6 additions & 0 deletions src/dev/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
target_sources(scratchcpp
PRIVATE
scriptbuilder.cpp
scriptbuilder_p.cpp
scriptbuilder_p.h
)
198 changes: 198 additions & 0 deletions src/dev/test/scriptbuilder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// SPDX-License-Identifier: Apache-2.0

#include <scratchcpp/dev/test/scriptbuilder.h>
#include <scratchcpp/block.h>
#include <scratchcpp/input.h>
#include <scratchcpp/field.h>
#include <scratchcpp/dev/compilerconstant.h>
#include <scratchcpp/dev/executablecode.h>
#include <scratchcpp/stage.h>
#include <scratchcpp/iengine.h>
#include <scratchcpp/list.h>

#include "scriptbuilder_p.h"

using namespace libscratchcpp;
using namespace libscratchcpp::test;

static std::unordered_map<IEngine *, std::shared_ptr<List>> captureLists;

/*! Constructs ScriptBuilder. */
ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target) :
impl(spimpl::make_unique_impl<ScriptBuilderPrivate>(engine, target))
{
// Create capture list
if (captureLists.find(engine) != captureLists.cend()) {
std::cerr << "error: only one ScriptBuilder can be created for each engine" << std::endl;
return;
}

captureLists[engine] = std::make_shared<List>("", "");

// Add start hat block
auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "script_builder_init");
engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
compiler->engine()->addGreenFlagScript(compiler->block());
return nullptr;
});
addBlock(block);

// Add compile function for return value capture block
engine->addCompileFunction(extension, "script_builder_capture", [](Compiler *compiler) -> CompilerValue * {
CompilerValue *input = compiler->addInput("VALUE");
compiler->createListAppend(captureLists[compiler->engine()].get(), input);
return nullptr;
});
}

/*! Destroys ScriptBuilder. */
ScriptBuilder::~ScriptBuilder()
{
captureLists.erase(impl->engine);
}

/*! Adds a block with the given opcode to the script. */
void ScriptBuilder::addBlock(const std::string &opcode)
{
impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), 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<Block>(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;
addBlock("script_builder_capture");
addObscuredInput("VALUE", valueBlock);
}

/*! Adds a simple input with a value to the current block. */
void ScriptBuilder::addValueInput(const std::string &name, const Value &value)
{
if (!impl->lastBlock)
return;

auto input = std::make_shared<Input>(name, Input::Type::Shadow);
input->setPrimaryValue(value);
impl->lastBlock->addInput(input);
}

/*! Adds a null input (zero) to the current block. */
void ScriptBuilder::addNullInput(const std::string &name)
{
if (!impl->lastBlock)
return;

auto input = std::make_shared<Input>(name, Input::Type::Shadow);
impl->lastBlock->addInput(input);
}

/*! Adds an input obscured by the given block to the current block. */
void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Block> valueBlock)
{
if (!impl->lastBlock)
return;

valueBlock->setId(std::to_string(impl->blockId++));
impl->inputBlocks.push_back(valueBlock);

auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
input->setValueBlock(valueBlock);
impl->lastBlock->addInput(input);
}

/*! Adds an input obscured by a block which returns zero to the current block. */
void ScriptBuilder::addNullObscuredInput(const std::string &name)
{
if (!impl->lastBlock)
return;

auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "");
block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
input->setValueBlock(block);
impl->inputBlocks.push_back(block);
impl->blocks.back()->addInput(input);
}

/*! Adds a dropdown menu input to the current block. */
void ScriptBuilder::addDropdownInput(const std::string &name, const std::string &selectedValue)
{
if (!impl->lastBlock)
return;

auto block = impl->blocks.back();
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
block->addInput(input);

auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), block->opcode() + "_menu");
menu->setShadow(true);
impl->inputBlocks.push_back(menu);
input->setValueBlock(menu);

auto field = std::make_shared<Field>(name, selectedValue);
menu->addField(field);
}

/*! Adds a dropdown field to the current block. */
void ScriptBuilder::addDropdownField(const std::string &name, const std::string &selectedValue)
{
if (!impl->lastBlock)
return;

auto field = std::make_shared<Field>(name, selectedValue);
impl->blocks.back()->addField(field);
}

/*! Builds and compiles the script. */
void ScriptBuilder::build()
{
if (impl->target->blocks().empty()) {
for (auto block : impl->blocks)
impl->target->addBlock(block);

for (auto block : impl->inputBlocks)
impl->target->addBlock(block);
}

std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();

if (std::find(targets.begin(), targets.end(), impl->target) == targets.end()) {
targets.push_back(impl->target);
impl->engine->setTargets({ impl->target });
}

impl->engine->compile();
}

/*! Runs the built script. */
void ScriptBuilder::run()
{
impl->engine->run();
}

/*! Returns the list of captured block return values. */
List *ScriptBuilder::capturedValues() const
{
return captureLists[impl->engine].get();
}

void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
{
if (!impl->blocks.empty()) {
auto lastBlock = impl->blocks.back();
lastBlock->setNext(block);
block->setParent(lastBlock);
}

impl->blocks.push_back(block);
}
12 changes: 12 additions & 0 deletions src/dev/test/scriptbuilder_p.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: Apache-2.0

#include "scriptbuilder_p.h"

using namespace libscratchcpp;
using namespace libscratchcpp::test;

ScriptBuilderPrivate::ScriptBuilderPrivate(IEngine *engine, std::shared_ptr<Target> target) :
engine(engine),
target(target)
{
}
35 changes: 35 additions & 0 deletions src/dev/test/scriptbuilder_p.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include <memory>
#include <vector>

namespace libscratchcpp
{

class IEngine;
class Target;
class Block;
class List;

} // namespace libscratchcpp

namespace libscratchcpp::test
{

class ScriptBuilderPrivate
{
public:
ScriptBuilderPrivate(IEngine *engine, std::shared_ptr<Target> target);
ScriptBuilderPrivate(const ScriptBuilderPrivate &) = delete;

IEngine *engine = nullptr;
std::shared_ptr<Target> target;
std::shared_ptr<Block> lastBlock;
std::vector<std::shared_ptr<Block>> blocks;
std::vector<std::shared_ptr<Block>> inputBlocks;
unsigned int blockId = 0;
};

} // namespace libscratchcpp::test
1 change: 1 addition & 0 deletions test/dev/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ add_subdirectory(executioncontext)
add_subdirectory(llvm)
add_subdirectory(compiler)
add_subdirectory(promise)
add_subdirectory(test_api)
25 changes: 25 additions & 0 deletions test/dev/test_api/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
add_library(
test_api_test_deps SHARED
testextension.cpp
testextension.h
)

target_link_libraries(
test_api_test_deps
GTest::gtest_main
scratchcpp
)

add_executable(
test_api_test
scriptbuilder_test.cpp
)

target_link_libraries(
test_api_test
GTest::gtest_main
scratchcpp
test_api_test_deps
)

gtest_discover_tests(test_api_test)
Loading
Loading