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
11 changes: 9 additions & 2 deletions include/scratchcpp/dev/test/scriptbuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ class ScriptBuilderPrivate;
class LIBSCRATCHCPP_EXPORT ScriptBuilder
{
public:
ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target);
ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target, bool createHatBlock = true);
ScriptBuilder(const ScriptBuilder &) = delete;

~ScriptBuilder();

void addBlock(std::shared_ptr<Block> block);
void addBlock(const std::string &opcode);
void captureBlockReturnValue();

Expand All @@ -53,9 +54,15 @@ class LIBSCRATCHCPP_EXPORT ScriptBuilder

List *capturedValues() const;

static void buildMultiple(const std::vector<ScriptBuilder *> &builders);

private:
void addBlock(std::shared_ptr<Block> block);
void addBlockToList(std::shared_ptr<Block> block);
void build(std::shared_ptr<Target> target);
std::string nextId();

static void addBlocksToTarget(Target *target, const std::vector<std::shared_ptr<Block>> &blocks);
static void addTargetToEngine(IEngine *engine, std::shared_ptr<Target> target);

spimpl::unique_impl_ptr<ScriptBuilderPrivate> impl;
};
Expand Down
36 changes: 36 additions & 0 deletions src/dev/blocks/customblocks.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// SPDX-License-Identifier: Apache-2.0

#include <scratchcpp/iengine.h>
#include <scratchcpp/dev/compiler.h>
#include <scratchcpp/dev/compilerconstant.h>
#include <scratchcpp/block.h>
#include <scratchcpp/field.h>

#include "customblocks.h"

using namespace libscratchcpp;
Expand All @@ -16,4 +22,34 @@ std::string CustomBlocks::description() const

void CustomBlocks::registerBlocks(IEngine *engine)
{
engine->addCompileFunction(this, "procedures_definition", [](Compiler *) -> CompilerValue * { return nullptr; });
engine->addCompileFunction(this, "procedures_call", &compileCall);
engine->addCompileFunction(this, "argument_reporter_boolean", &compileArgument);
engine->addCompileFunction(this, "argument_reporter_string_number", &compileArgument);
}

CompilerValue *CustomBlocks::compileCall(Compiler *compiler)
{
auto block = compiler->block();
auto prototype = block->mutationPrototype();
const std::vector<std::string> &procedureArgs = prototype->argumentIds();
Compiler::Args args;

for (size_t i = 0; i < procedureArgs.size(); i++) {
auto index = block->findInput(procedureArgs[i]);

if (index == -1)
args.push_back(compiler->addConstValue(Value()));
else
args.push_back(compiler->addInput(block->inputAt(index).get()));
}

compiler->createProcedureCall(compiler->block()->mutationPrototype(), args);
return nullptr;
}

CompilerValue *CustomBlocks::compileArgument(Compiler *compiler)
{
const std::string &argName = compiler->field("VALUE")->value().toString();
return compiler->addProcedureArgument(argName);
}
4 changes: 4 additions & 0 deletions src/dev/blocks/customblocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ class CustomBlocks : public IExtension
std::string description() const override;

void registerBlocks(IEngine *engine) override;

private:
static CompilerValue *compileCall(Compiler *compiler);
static CompilerValue *compileArgument(Compiler *compiler);
};

} // namespace libscratchcpp
114 changes: 81 additions & 33 deletions src/dev/test/scriptbuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,47 +16,53 @@
using namespace libscratchcpp;
using namespace libscratchcpp::test;

static std::unordered_map<IEngine *, std::shared_ptr<List>> captureLists;
// TODO: Add support for return values captures when building multiple scripts
static std::unordered_map<const ScriptBuilder *, std::shared_ptr<List>> captureLists;
static ScriptBuilder *currentScriptBuilder = nullptr;

/*! Constructs ScriptBuilder. */
ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target) :
ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target, bool createHatBlock) :
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>("", "");
captureLists[this] = 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);
if (createHatBlock) {
auto block = std::make_shared<Block>(nextId(), "script_builder_init");
engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
compiler->engine()->addGreenFlagScript(compiler->block());
return nullptr;
});
addBlockToList(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);
compiler->createListAppend(captureLists[currentScriptBuilder].get(), input);
return nullptr;
});
}

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

/*! Adds the given block to the script. */
void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
{
block->setId(nextId());
impl->lastBlock = block;
addBlockToList(block);
}

/*! 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);
addBlock(std::make_shared<Block>("", opcode));
}

/*! Captures the return value of the created reporter block. It can be retrieved using capturedValues() later. */
Expand Down Expand Up @@ -101,7 +107,7 @@ void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Bl
block->setParent(impl->lastBlock);

while (block) {
block->setId(std::to_string(impl->blockId++));
block->setId(nextId());
impl->inputBlocks.push_back(block);

auto parent = block->parent();
Expand All @@ -128,7 +134,7 @@ void ScriptBuilder::addNullObscuredInput(const std::string &name)
return;

auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "");
auto block = std::make_shared<Block>(nextId(), "");
block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
input->setValueBlock(block);
impl->inputBlocks.push_back(block);
Expand All @@ -144,7 +150,7 @@ void ScriptBuilder::addDropdownInput(const std::string &name, const std::string
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
impl->lastBlock->addInput(input);

auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), impl->lastBlock->opcode() + "_menu");
auto menu = std::make_shared<Block>(nextId(), impl->lastBlock->opcode() + "_menu");
menu->setShadow(true);
impl->inputBlocks.push_back(menu);
input->setValueBlock(menu);
Expand All @@ -170,7 +176,7 @@ void ScriptBuilder::addEntityInput(const std::string &name, const std::string &e
return;

if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
entity->setId(std::to_string(impl->blockId++));
entity->setId(nextId());
impl->entities.push_back(entity);
}

Expand All @@ -188,7 +194,7 @@ void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptr<Enti
return;

if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) {
entity->setId(std::to_string(impl->blockId++));
entity->setId(nextId());
impl->entities.push_back(entity);
}

Expand Down Expand Up @@ -218,6 +224,10 @@ std::shared_ptr<Block> ScriptBuilder::currentBlock()
target->addList(list);

build(target);

std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
targets.erase(std::remove(targets.begin(), targets.end(), target), targets.end());
impl->engine->setTargets(targets);
}

return impl->lastBlock;
Expand Down Expand Up @@ -256,10 +266,31 @@ void ScriptBuilder::run()
/*! Returns the list of captured block return values. */
List *ScriptBuilder::capturedValues() const
{
return captureLists[impl->engine].get();
return captureLists[this].get();
}

void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
/*!
* Builds multiple scripts using the given script builders.
* \note Using run() on any of the script builders will result in all scripts without a custom hat block being called. Use this only with a single when flag clicked block.
* \note Return value capturing is not supported when building multiple scripts.
*/
void ScriptBuilder::buildMultiple(const std::vector<ScriptBuilder *> &builders)
{
std::unordered_set<IEngine *> engines;

for (ScriptBuilder *builder : builders) {
auto target = builder->impl->target;
addBlocksToTarget(target.get(), builder->impl->blocks);
addBlocksToTarget(target.get(), builder->impl->inputBlocks);
addTargetToEngine(builder->impl->engine, target);
engines.insert(builder->impl->engine);
}

for (IEngine *engine : engines)
engine->compile();
}

void ScriptBuilder::addBlockToList(std::shared_ptr<Block> block)
{
if (!impl->blocks.empty()) {
auto lastBlock = impl->blocks.back();
Expand All @@ -272,20 +303,37 @@ void ScriptBuilder::addBlock(std::shared_ptr<Block> block)

void ScriptBuilder::build(std::shared_ptr<Target> target)
{
if (target->blocks().empty()) {
for (auto block : impl->blocks)
target->addBlock(block);
currentScriptBuilder = this;

addBlocksToTarget(target.get(), impl->blocks);
addBlocksToTarget(target.get(), impl->inputBlocks);
addTargetToEngine(impl->engine, target);

impl->engine->compile();
currentScriptBuilder = nullptr;
}

std::string ScriptBuilder::nextId()
{
return std::to_string((uintptr_t)this) + '.' + std::to_string(impl->blockId++);
}

void ScriptBuilder::addBlocksToTarget(Target *target, const std::vector<std::shared_ptr<Block>> &blocks)
{
auto targetBlocks = target->blocks();

for (auto block : impl->inputBlocks)
for (auto block : blocks) {
if (std::find(targetBlocks.begin(), targetBlocks.end(), block) == targetBlocks.end())
target->addBlock(block);
}
}

std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
void ScriptBuilder::addTargetToEngine(IEngine *engine, std::shared_ptr<Target> target)
{
std::vector<std::shared_ptr<Target>> targets = engine->targets();

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

impl->engine->compile();
}
1 change: 1 addition & 0 deletions test/dev/blocks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ if (LIBSCRATCHCPP_ENABLE_CUSTOM_BLOCKS)
GTest::gmock_main
scratchcpp
scratchcpp_mocks
block_test_deps
)

gtest_discover_tests(custom_blocks_test)
Expand Down
Loading
Loading