|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | + |
| 3 | +#include <scratchcpp/dev/compiler.h> |
| 4 | +#include <scratchcpp/block.h> |
| 5 | +#include <scratchcpp/input.h> |
| 6 | +#include <scratchcpp/inputvalue.h> |
| 7 | + |
| 8 | +#include "compiler_p.h" |
| 9 | +#include "internal/icodebuilderfactory.h" |
| 10 | +#include "internal/icodebuilder.h" |
| 11 | + |
| 12 | +using namespace libscratchcpp; |
| 13 | + |
| 14 | +/*! Constructs Compiler. */ |
| 15 | +Compiler::Compiler(IEngine *engine, Target *target) : |
| 16 | + impl(spimpl::make_unique_impl<CompilerPrivate>(engine, target)) |
| 17 | +{ |
| 18 | +} |
| 19 | + |
| 20 | +/*! Returns the Engine of the project. */ |
| 21 | +IEngine *Compiler::engine() const |
| 22 | +{ |
| 23 | + return impl->engine; |
| 24 | +} |
| 25 | + |
| 26 | +/*! Returns the Target of this compiler. */ |
| 27 | +Target *Compiler::target() const |
| 28 | +{ |
| 29 | + return impl->target; |
| 30 | +} |
| 31 | + |
| 32 | +/*! Returns currently compiled block. */ |
| 33 | +std::shared_ptr<libscratchcpp::Block> Compiler::block() const |
| 34 | +{ |
| 35 | + return impl->block; |
| 36 | +} |
| 37 | + |
| 38 | +/*! Compiles the script starting with the given block. */ |
| 39 | +std::shared_ptr<ExecutableCode> Compiler::compile(std::shared_ptr<Block> startBlock) |
| 40 | +{ |
| 41 | + impl->builder = impl->builderFactory->create(startBlock->id()); |
| 42 | + impl->warp = false; |
| 43 | + impl->block = startBlock; |
| 44 | + |
| 45 | + while (impl->block) { |
| 46 | + size_t substacks = impl->substackTree.size(); |
| 47 | + |
| 48 | + if (impl->block->compileFunction()) |
| 49 | + impl->block->compile(this); |
| 50 | + else { |
| 51 | + std::cout << "warning: unsupported block: " << impl->block->opcode() << std::endl; |
| 52 | + impl->unsupportedBlocks.insert(impl->block->opcode()); |
| 53 | + } |
| 54 | + |
| 55 | + if (substacks != impl->substackTree.size()) |
| 56 | + continue; |
| 57 | + |
| 58 | + impl->block = impl->block->next(); |
| 59 | + |
| 60 | + if (!impl->block && !impl->substackTree.empty()) |
| 61 | + impl->substackEnd(); |
| 62 | + } |
| 63 | + |
| 64 | + return impl->builder->finalize(); |
| 65 | +} |
| 66 | + |
| 67 | +/*! |
| 68 | + * Adds a call to the given function.\n |
| 69 | + * For example: extern "C" some_block(ValueData *ret, ValueData *arg1, ValueData *arg2) has 2 arguments |
| 70 | + */ |
| 71 | +void Compiler::addFunctionCall(const std::string &functionName, int argCount, bool returns) |
| 72 | +{ |
| 73 | + impl->builder->addFunctionCall(functionName, argCount, returns); |
| 74 | +} |
| 75 | + |
| 76 | +/*! Adds a constant value to the compiled code. */ |
| 77 | +void Compiler::addConstValue(const Value &value) |
| 78 | +{ |
| 79 | + impl->builder->addConstValue(value); |
| 80 | +} |
| 81 | + |
| 82 | +/*! Adds the given variable to the code (to read it). */ |
| 83 | +void Compiler::addVariableValue(Variable *variable) |
| 84 | +{ |
| 85 | + impl->builder->addVariableValue(variable); |
| 86 | +} |
| 87 | + |
| 88 | +/*! Adds the given list to the code (to read its string representation). */ |
| 89 | +void Compiler::addListContents(List *list) |
| 90 | +{ |
| 91 | + impl->builder->addListContents(list); |
| 92 | +} |
| 93 | + |
| 94 | +/*! Compiles the given input (resolved by name) and adds it to the compiled code. */ |
| 95 | +void Compiler::addInput(const std::string &name) |
| 96 | +{ |
| 97 | + addInput(impl->block->inputAt(impl->block->findInput(name)).get()); |
| 98 | +} |
| 99 | + |
| 100 | +/*! Jumps to the given if substack. */ |
| 101 | +void Compiler::moveToIf(std::shared_ptr<Block> substack) |
| 102 | +{ |
| 103 | + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::IfStatement }); |
| 104 | + impl->block = substack; |
| 105 | + |
| 106 | + if (!impl->block) |
| 107 | + impl->substackEnd(); |
| 108 | +} |
| 109 | + |
| 110 | +/*! Jumps to the given if/else substack. The second substack is used for the else branch. */ |
| 111 | +void Compiler::moveToIfElse(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2) |
| 112 | +{ |
| 113 | + impl->substackTree.push_back({ { impl->block, substack2 }, CompilerPrivate::SubstackType::IfStatement }); |
| 114 | + impl->block = substack1; |
| 115 | + |
| 116 | + if (!impl->block) |
| 117 | + impl->substackEnd(); |
| 118 | +} |
| 119 | + |
| 120 | +/*! Jumps to the given loop substack. */ |
| 121 | +void Compiler::moveToLoop(std::shared_ptr<Block> substack) |
| 122 | +{ |
| 123 | + impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); |
| 124 | + impl->block = substack; |
| 125 | + |
| 126 | + if (!impl->block) |
| 127 | + impl->substackEnd(); |
| 128 | +} |
| 129 | + |
| 130 | +/*! Makes current script run without screen refresh. */ |
| 131 | +void Compiler::warp() |
| 132 | +{ |
| 133 | + impl->warp = true; |
| 134 | +} |
| 135 | + |
| 136 | +/*! Convenience method which returns the field with the given name. */ |
| 137 | +Field *Compiler::field(const std::string &name) const |
| 138 | +{ |
| 139 | + return impl->block->fieldAt(impl->block->findField(name)).get(); |
| 140 | +} |
| 141 | + |
| 142 | +/*! Returns unsupported block opcodes which were found when compiling. */ |
| 143 | +const std::unordered_set<std::string> &Compiler::unsupportedBlocks() const |
| 144 | +{ |
| 145 | + return impl->unsupportedBlocks; |
| 146 | +} |
| 147 | + |
| 148 | +void Compiler::addInput(Input *input) |
| 149 | +{ |
| 150 | + if (!input) { |
| 151 | + addConstValue(Value()); |
| 152 | + return; |
| 153 | + } |
| 154 | + |
| 155 | + switch (input->type()) { |
| 156 | + case Input::Type::Shadow: |
| 157 | + case Input::Type::NoShadow: { |
| 158 | + if (input->pointsToDropdownMenu()) |
| 159 | + addConstValue(input->selectedMenuItem()); |
| 160 | + else { |
| 161 | + auto previousBlock = impl->block; |
| 162 | + impl->block = input->valueBlock(); |
| 163 | + |
| 164 | + if (impl->block) { |
| 165 | + if (impl->block->compileFunction()) |
| 166 | + impl->block->compile(this); |
| 167 | + else { |
| 168 | + std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; |
| 169 | + impl->unsupportedBlocks.insert(impl->block->opcode()); |
| 170 | + addConstValue(Value()); |
| 171 | + } |
| 172 | + } else |
| 173 | + addConstValue(input->primaryValue()->value()); |
| 174 | + |
| 175 | + impl->block = previousBlock; |
| 176 | + } |
| 177 | + |
| 178 | + break; |
| 179 | + } |
| 180 | + |
| 181 | + case Input::Type::ObscuredShadow: { |
| 182 | + auto previousBlock = impl->block; |
| 183 | + impl->block = input->valueBlock(); |
| 184 | + if (impl->block) { |
| 185 | + if (impl->block->compileFunction()) |
| 186 | + impl->block->compile(this); |
| 187 | + else { |
| 188 | + std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; |
| 189 | + impl->unsupportedBlocks.insert(impl->block->opcode()); |
| 190 | + addConstValue(Value()); |
| 191 | + } |
| 192 | + } else |
| 193 | + input->primaryValue()->compile(this); |
| 194 | + |
| 195 | + impl->block = previousBlock; |
| 196 | + break; |
| 197 | + } |
| 198 | + } |
| 199 | +} |
0 commit comments