diff --git a/CMakeLists.txt b/CMakeLists.txt index 29f589fa..810a5382 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.14) -project(libscratchcpp VERSION 0.12.0 LANGUAGES C CXX) +project(libscratchcpp VERSION 0.13.0 LANGUAGES C CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) @@ -21,6 +21,7 @@ add_library(scratchcpp SHARED) add_subdirectory(src) include_directories(src) # TODO: Remove this line include_directories(include) +install(TARGETS scratchcpp DESTINATION lib) if (LIBSCRATCHCPP_COMPUTED_GOTO) target_compile_definitions(scratchcpp PRIVATE ENABLE_COMPUTED_GOTO) @@ -59,6 +60,8 @@ target_sources(scratchcpp include/scratchcpp/sprite.h include/scratchcpp/textbubble.h include/scratchcpp/itimer.h + include/scratchcpp/istacktimer.h + include/scratchcpp/irandomgenerator.h include/scratchcpp/keyevent.h include/scratchcpp/rect.h include/scratchcpp/igraphicseffect.h @@ -72,8 +75,14 @@ if (LIBSCRATCHCPP_USE_LLVM) target_sources(scratchcpp PUBLIC include/scratchcpp/dev/compiler.h + include/scratchcpp/dev/compilercontext.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 + include/scratchcpp/dev/test/scriptbuilder.h ) if(LIBSCRATCHCPP_PRINT_LLVM_IR) @@ -87,26 +96,27 @@ else() endif() include(FetchContent) -set(ZIP_SRC thirdparty/zip/src) -set(UTFCPP_SRC thirdparty/utfcpp/source) -add_library(zip SHARED - ${ZIP_SRC}/zip.c - ${ZIP_SRC}/zip.h - ${ZIP_SRC}/miniz.h -) -target_include_directories(scratchcpp PUBLIC ${ZIP_SRC}) +# zip +include(build/zip.cmake) +target_link_libraries(scratchcpp PRIVATE zip) +# utfcpp +set(UTFCPP_SRC thirdparty/utfcpp/source) target_include_directories(scratchcpp PUBLIC ${UTFCPP_SRC}) + +# spimpl include_directories(thirdparty/spimpl) +# JSON FetchContent_Declare(json URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz) FetchContent_MakeAvailable(json) - target_link_libraries(scratchcpp PRIVATE nlohmann_json::nlohmann_json) -target_link_libraries(scratchcpp PRIVATE zip) + +# Audio target_link_libraries(scratchcpp PRIVATE scratchcpp-audio) +# Network if (LIBSCRATCHCPP_NETWORK_SUPPORT) FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git GIT_TAG 225b7454877805f089b3895260438e929bd6d123) # 09-22-2024 @@ -115,18 +125,25 @@ if (LIBSCRATCHCPP_NETWORK_SUPPORT) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_NETWORK_SUPPORT) endif() +# LLVM if (LIBSCRATCHCPP_USE_LLVM) include(build/HunterPackages.cmake) include(build/LLVM.cmake) target_link_libraries(scratchcpp PRIVATE LLVM) endif() +if(LIBSCRATCHCPP_PRINT_LLVM_IR) + target_compile_definitions(scratchcpp PRIVATE PRINT_LLVM_IR) +endif() + +# Macros target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_LIBRARY) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION="${PROJECT_VERSION}") target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_MINOR=${PROJECT_VERSION_MINOR}) target_compile_definitions(scratchcpp PRIVATE LIBSCRATCHCPP_VERSION_PATCH=${PROJECT_VERSION_PATCH}) +# Unit tests if (LIBSCRATCHCPP_BUILD_UNIT_TESTS) enable_testing() add_subdirectory(test) diff --git a/build/HunterPackages.cmake b/build/HunterPackages.cmake index 9dc8f703..7bf566f3 100644 --- a/build/HunterPackages.cmake +++ b/build/HunterPackages.cmake @@ -11,7 +11,6 @@ set(HUNTER_SHA1 "4A018750743AC656A859C99C655723EAF68EE038") set(HUNTER_LLVM_VERSION 19.1.0) set(HUNTER_LLVM_CMAKE_ARGS LLVM_ENABLE_CRASH_OVERRIDES=OFF - LLVM_ENABLE_ASSERTIONS=ON LLVM_ENABLE_ZLIB=OFF LLVM_ENABLE_RTTI=ON LLVM_BUILD_EXAMPLES=OFF @@ -21,6 +20,12 @@ set(HUNTER_LLVM_CMAKE_ARGS LLVM_TARGETS_TO_BUILD=host ) +if(CMAKE_BUILD_TYPE EQUAL "Debug") + set(HUNTER_LLVM_CMAKE_ARGS ${HUNTER_LLVM_CMAKE_ARGS} LLVM_ENABLE_ASSERTIONS=ON) +else() + set(HUNTER_LLVM_CMAKE_ARGS ${HUNTER_LLVM_CMAKE_ARGS} LLVM_ENABLE_ASSERTIONS=OFF) +endif() + set(HUNTER_PACKAGES LLVM) include(FetchContent) diff --git a/build/zip.cmake b/build/zip.cmake new file mode 100644 index 00000000..1165aba8 --- /dev/null +++ b/build/zip.cmake @@ -0,0 +1,10 @@ +set(ZIP_SRC ${PROJECT_SOURCE_DIR}/thirdparty/zip/src) + +add_library(zip SHARED + ${ZIP_SRC}/zip.c + ${ZIP_SRC}/zip.h + ${ZIP_SRC}/miniz.h +) + +target_include_directories(zip PUBLIC ${ZIP_SRC}) +install(TARGETS zip DESTINATION lib) diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h index 975c3e16..c5c10d88 100644 --- a/include/scratchcpp/asset.h +++ b/include/scratchcpp/asset.h @@ -9,6 +9,7 @@ namespace libscratchcpp { +class Target; class AssetPrivate; /*! \brief The Asset class represents a Scratch asset, for example a Costume or a Sound. */ @@ -32,6 +33,9 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity unsigned int dataSize() const; void setData(unsigned int size, void *data); + Target *target() const; + void setTarget(Target *target); + protected: virtual void processData(unsigned int size, void *data) { } diff --git a/include/scratchcpp/block.h b/include/scratchcpp/block.h index fe6cf382..76c70dc1 100644 --- a/include/scratchcpp/block.h +++ b/include/scratchcpp/block.h @@ -10,6 +10,9 @@ namespace libscratchcpp class IEngine; class Target; +#ifdef USE_LLVM +class CompilerValue; +#endif class Input; class Field; class Comment; @@ -25,7 +28,11 @@ class LIBSCRATCHCPP_EXPORT Block : public Entity Block(const std::string &id, const std::string &opcode); Block(const Block &) = delete; +#ifdef USE_LLVM + CompilerValue *compile(Compiler *compiler); +#else void compile(Compiler *compiler); +#endif const std::string &opcode() const; @@ -76,7 +83,7 @@ class LIBSCRATCHCPP_EXPORT Block : public Entity BlockComp compileFunction() const; void setCompileFunction(BlockComp newCompileFunction); - BlockComp hatPredicateCompileFunction() const; + HatPredicateCompileFunc hatPredicateCompileFunction() const; void setHatPredicateCompileFunction(HatPredicateCompileFunc newHatPredicateCompileFunction); bool mutationHasNext() const; diff --git a/include/scratchcpp/dev/compiler.h b/include/scratchcpp/dev/compiler.h index 3b579023..db7ada83 100644 --- a/include/scratchcpp/dev/compiler.h +++ b/include/scratchcpp/dev/compiler.h @@ -11,13 +11,18 @@ namespace libscratchcpp { +class CompilerContext; class IEngine; class Target; class ExecutableCode; +class CompilerValue; +class CompilerConstant; +class CompilerLocalVariable; class Variable; class List; class Input; class Field; +class BlockPrototype; class CompilerPrivate; /*! \brief The Compiler class provides API for compiling Scratch scripts. */ @@ -33,6 +38,10 @@ class LIBSCRATCHCPP_EXPORT Compiler Unknown }; + using ArgTypes = std::vector; + using Args = std::vector; + + Compiler(CompilerContext *ctx); Compiler(IEngine *engine, Target *target); Compiler(const Compiler &) = delete; @@ -41,42 +50,102 @@ class LIBSCRATCHCPP_EXPORT Compiler std::shared_ptr block() const; std::shared_ptr compile(std::shared_ptr startBlock); - - void addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const std::vector &argTypes = {}); - void addConstValue(const Value &value); - void addVariableValue(Variable *variable); - void addListContents(List *list); - void addInput(const std::string &name); - - void createAdd(); - void createSub(); - void createMul(); - void createDiv(); - - void createCmpEQ(); - void createCmpGT(); - void createCmpLT(); - - void createAnd(); - void createOr(); - void createNot(); - - void moveToIf(std::shared_ptr substack); - void moveToIfElse(std::shared_ptr substack1, std::shared_ptr substack2); - void moveToRepeatLoop(std::shared_ptr substack); - void moveToWhileLoop(std::shared_ptr substack); - void moveToRepeatUntilLoop(std::shared_ptr substack); + void preoptimize(); + + CompilerValue *addFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {}); + CompilerValue *addTargetFunctionCall(const std::string &functionName, StaticType returnType = StaticType::Void, const ArgTypes &argTypes = {}, const Args &args = {}); + 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); + CompilerValue *addListItemIndex(List *list, CompilerValue *item); + CompilerValue *addListContains(List *list, CompilerValue *item); + CompilerValue *addListSize(List *list); + CompilerValue *addProcedureArgument(const std::string &name); + + CompilerValue *addInput(const std::string &name); + CompilerValue *addInput(Input *input); + + CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2); + CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2); + CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2); + 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); + CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2); + + CompilerValue *createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive = false); + + CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2); + CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2); + CompilerValue *createNot(CompilerValue *operand); + + CompilerValue *createMod(CompilerValue *num1, CompilerValue *num2); + CompilerValue *createRound(CompilerValue *num); + CompilerValue *createAbs(CompilerValue *num); + CompilerValue *createFloor(CompilerValue *num); + CompilerValue *createCeil(CompilerValue *num); + CompilerValue *createSqrt(CompilerValue *num); + CompilerValue *createSin(CompilerValue *num); + CompilerValue *createCos(CompilerValue *num); + CompilerValue *createTan(CompilerValue *num); + CompilerValue *createAsin(CompilerValue *num); + CompilerValue *createAcos(CompilerValue *num); + CompilerValue *createAtan(CompilerValue *num); + CompilerValue *createLn(CompilerValue *num); + CompilerValue *createLog10(CompilerValue *num); + CompilerValue *createExp(CompilerValue *num); + CompilerValue *createExp10(CompilerValue *num); + + 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); + void createListRemove(List *list, CompilerValue *index); + void createListAppend(List *list, CompilerValue *item); + void createListInsert(List *list, CompilerValue *index, CompilerValue *item); + void createListReplace(List *list, CompilerValue *index, CompilerValue *item); + + void beginIfStatement(CompilerValue *cond); + void beginElseBranch(); + void endIf(); + + void beginWhileLoop(CompilerValue *cond); + void beginRepeatUntilLoop(CompilerValue *cond); void beginLoopCondition(); + void endLoop(); + + void moveToIf(CompilerValue *cond, std::shared_ptr substack); + void moveToIfElse(CompilerValue *cond, std::shared_ptr substack1, std::shared_ptr substack2); + void moveToRepeatLoop(CompilerValue *count, std::shared_ptr substack); + void moveToWhileLoop(CompilerValue *cond, std::shared_ptr substack); + void moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr substack); void warp(); + void createYield(); + void createStop(); + + void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args); + Input *input(const std::string &name) const; Field *field(const std::string &name) const; const std::unordered_set &unsupportedBlocks() const; - private: - void addInput(Input *input); + static std::shared_ptr createContext(IEngine *engine, Target *target); + private: spimpl::unique_impl_ptr impl; }; diff --git a/include/scratchcpp/dev/compilerconstant.h b/include/scratchcpp/dev/compilerconstant.h new file mode 100644 index 00000000..51d6347b --- /dev/null +++ b/include/scratchcpp/dev/compilerconstant.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "compilervalue.h" + +namespace libscratchcpp +{ + +class CompilerConstantPrivate; + +/*! \brief The CompilerConstant class represents a constant value in compiled code. */ +class LIBSCRATCHCPP_EXPORT CompilerConstant : public CompilerValue +{ + public: + CompilerConstant(Compiler::StaticType type, const Value &value); + CompilerConstant(const CompilerConstant &) = delete; + + bool isConst() const override final { return true; }; + + const Value &value() const; + + private: + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/dev/compilercontext.h b/include/scratchcpp/dev/compilercontext.h new file mode 100644 index 00000000..8038c5fe --- /dev/null +++ b/include/scratchcpp/dev/compilercontext.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../global.h" +#include "../spimpl.h" + +namespace libscratchcpp +{ + +class IEngine; +class Target; +class CompilerContextPrivate; + +/*! \brief The CompilerContext represents a context for a specific target which is used with the Compiler class. */ +class LIBSCRATCHCPP_EXPORT CompilerContext +{ + public: + CompilerContext(IEngine *engine, Target *target); + CompilerContext(const CompilerContext &) = delete; + virtual ~CompilerContext() { } + + IEngine *engine() const; + Target *target() const; + + /*! + * Optimizes compiled scripts ahead of time. + * \see Compiler#preoptimize() + */ + virtual void preoptimize() { } + + private: + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp 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/compilervalue.h b/include/scratchcpp/dev/compilervalue.h new file mode 100644 index 00000000..c783d8ac --- /dev/null +++ b/include/scratchcpp/dev/compilervalue.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "compiler.h" + +namespace libscratchcpp +{ + +class CompilerValuePrivate; + +/*! \brief The CompilerValue class represents a local value in compiled code. */ +class LIBSCRATCHCPP_EXPORT CompilerValue +{ + public: + CompilerValue(Compiler::StaticType type); + CompilerValue(const CompilerValue &) = delete; + virtual ~CompilerValue() { } + + Compiler::StaticType type() const; + void setType(Compiler::StaticType type); + + virtual bool isConst() const { return false; }; + + private: + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/dev/executablecode.h b/include/scratchcpp/dev/executablecode.h index 54efea83..8136e40e 100644 --- a/include/scratchcpp/dev/executablecode.h +++ b/include/scratchcpp/dev/executablecode.h @@ -10,7 +10,7 @@ namespace libscratchcpp { class ExecutionContext; -class Target; +class Thread; /*! \brief The ExecutableCode class represents the code of a compiled Scratch script. */ class LIBSCRATCHCPP_EXPORT ExecutableCode @@ -30,14 +30,8 @@ class LIBSCRATCHCPP_EXPORT ExecutableCode /*! Returns true if the code is stopped or finished. */ virtual bool isFinished(ExecutionContext *context) const = 0; - /*! Pauses the script (when it's executed using run() again) until resolvePromise() is called. */ - virtual void promise() = 0; - - /*! Resolves the promise and resumes the script. */ - virtual void resolvePromise() = 0; - /*! Creates an execution context for the given Target. */ - virtual std::shared_ptr createExecutionContext(Target *target) const = 0; + virtual std::shared_ptr createExecutionContext(Thread *thread) const = 0; }; } // namespace libscratchcpp diff --git a/include/scratchcpp/dev/executioncontext.h b/include/scratchcpp/dev/executioncontext.h index 7d7ac877..69d11945 100644 --- a/include/scratchcpp/dev/executioncontext.h +++ b/include/scratchcpp/dev/executioncontext.h @@ -8,18 +8,32 @@ namespace libscratchcpp { -class Target; +class Thread; +class IEngine; +class Promise; +class IStackTimer; +class IRandomGenerator; class ExecutionContextPrivate; /*! \brief The ExecutionContext represents the execution context of a target (can be a clone) with variables, lists, etc. */ class LIBSCRATCHCPP_EXPORT ExecutionContext { public: - ExecutionContext(Target *target); + ExecutionContext(Thread *thread); ExecutionContext(const ExecutionContext &) = delete; virtual ~ExecutionContext() { } - Target *target() const; + Thread *thread() const; + IEngine *engine() const; + + std::shared_ptr promise() const; + void setPromise(std::shared_ptr promise); + + IStackTimer *stackTimer() const; + void setStackTimer(IStackTimer *newStackTimer); + + IRandomGenerator *rng() const; + void setRng(IRandomGenerator *newRng); private: spimpl::unique_impl_ptr impl; diff --git a/include/scratchcpp/dev/promise.h b/include/scratchcpp/dev/promise.h new file mode 100644 index 00000000..2c0b6010 --- /dev/null +++ b/include/scratchcpp/dev/promise.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "../global.h" +#include "../spimpl.h" + +namespace libscratchcpp +{ + +class PromisePrivate; + +/*! \brief The Promise class represents the eventual completion of an asynchronous operation. */ +class LIBSCRATCHCPP_EXPORT Promise +{ + public: + Promise(); + Promise(const Promise &) = delete; + + bool isResolved() const; + void resolve(); + + private: + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/dev/test/scriptbuilder.h b/include/scratchcpp/dev/test/scriptbuilder.h new file mode 100644 index 00000000..a01db6b8 --- /dev/null +++ b/include/scratchcpp/dev/test/scriptbuilder.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "../../inputvalue.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, bool createHatBlock = true); + ScriptBuilder(const ScriptBuilder &) = delete; + + ~ScriptBuilder(); + + void addBlock(std::shared_ptr block); + void addBlock(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 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 addEntityInput(const std::string &name, const std::string &entityName, InputValue::Type entityType, std::shared_ptr entity); + void addEntityField(const std::string &name, std::shared_ptr entity); + + std::shared_ptr currentBlock(); + std::shared_ptr takeBlock(); + + void build(); + void run(); + + List *capturedValues() const; + + static void buildMultiple(const std::vector &builders); + + private: + void addBlockToList(std::shared_ptr block); + void build(std::shared_ptr target); + std::string nextId(); + + static void addBlocksToTarget(Target *target, const std::vector> &blocks); + static void addTargetToEngine(IEngine *engine, std::shared_ptr target); + + spimpl::unique_impl_ptr impl; +}; + +} // namespace libscratchcpp::test diff --git a/include/scratchcpp/global.h b/include/scratchcpp/global.h index c72c14f6..e5824008 100644 --- a/include/scratchcpp/global.h +++ b/include/scratchcpp/global.h @@ -25,6 +25,9 @@ namespace libscratchcpp class VirtualMachine; class Compiler; +#ifdef USE_LLVM +class CompilerValue; +#endif class Block; class Value; @@ -35,12 +38,21 @@ class Value; */ using BlockFunc = unsigned int (*)(VirtualMachine *vm); +#ifdef USE_LLVM +/*! + * \typedef BlockComp + * + * BlockComp is a function pointer for functions which are used to compile blocks. + */ +using BlockComp = CompilerValue *(*)(Compiler *); +#else /*! * \typedef BlockComp * * BlockComp is a function pointer for functions which are used to compile blocks to bytecode. */ using BlockComp = void (*)(Compiler *); +#endif // USE_LLVM /*! * \typedef MonitorNameFunc @@ -56,12 +68,21 @@ using MonitorNameFunc = const std::string &(*)(Block *); */ using MonitorChangeFunc = void (*)(Block *, const Value &newValue); +#ifdef USE_LLVM +/*! + * \typedef HatPredicateCompileFunc + * + * HatPredicateCompileFunc is a function pointer for functions which are used to compile edge-activated hat predicates. + */ +using HatPredicateCompileFunc = CompilerValue *(*)(Compiler *vm); +#else /*! * \typedef HatPredicateCompileFunc * * HatPredicateCompileFunc is a function pointer for functions which are used to compile edge-activated hat predicates to bytecode. */ using HatPredicateCompileFunc = void (*)(Compiler *vm); +#endif // USE_LLVM } // namespace libscratchcpp diff --git a/include/scratchcpp/iextension.h b/include/scratchcpp/iextension.h index a53b8356..0a4bff08 100644 --- a/include/scratchcpp/iextension.h +++ b/include/scratchcpp/iextension.h @@ -2,9 +2,7 @@ #pragma once -#include - -#include "global.h" +#include "value_functions.h" namespace libscratchcpp { @@ -27,6 +25,9 @@ class LIBSCRATCHCPP_EXPORT IExtension /*! Returns the description of the extension. */ virtual std::string description() const = 0; + /*! Returns the block color of the extension. */ + virtual Rgb color() const = 0; + /*! Override this method to register blocks. */ virtual void registerBlocks(IEngine *engine) = 0; diff --git a/include/scratchcpp/inputvalue.h b/include/scratchcpp/inputvalue.h index 32c4cb0f..f1580c75 100644 --- a/include/scratchcpp/inputvalue.h +++ b/include/scratchcpp/inputvalue.h @@ -10,6 +10,9 @@ namespace libscratchcpp { +#ifdef USE_LLVM +class CompilerValue; +#endif class Block; class Entity; class InputValuePrivate; @@ -34,7 +37,11 @@ class LIBSCRATCHCPP_EXPORT InputValue InputValue(Type type = Type::Number); +#ifdef USE_LLVM + CompilerValue *compile(Compiler *compiler); +#else void compile(Compiler *compiler); +#endif Type type() const; void setType(Type newType); diff --git a/include/scratchcpp/irandomgenerator.h b/include/scratchcpp/irandomgenerator.h new file mode 100644 index 00000000..1fa1d79c --- /dev/null +++ b/include/scratchcpp/irandomgenerator.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "global.h" + +namespace libscratchcpp +{ + +/*! \brief The IRandomGenerator interface represents a random number generator that can be received e. g. from an ExecutionContext. */ +class LIBSCRATCHCPP_EXPORT IRandomGenerator +{ + public: + virtual ~IRandomGenerator() { } + + /*! Returns a random integer in the given range (inclusive). */ + virtual long randint(long start, long end) const = 0; + + /*! Returns a random double in the given range (inclusive). */ + virtual double randintDouble(double start, double end) const = 0; + + /*! Returns a random integer in the given range (inclusive) except the given integer. */ + virtual long randintExcept(long start, long end, long except) const = 0; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/ispritehandler.h b/include/scratchcpp/ispritehandler.h index d8a61a68..e423f960 100644 --- a/include/scratchcpp/ispritehandler.h +++ b/include/scratchcpp/ispritehandler.h @@ -85,10 +85,10 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler virtual bool touchingPoint(double x, double y) const = 0; /*! Used to check whether the sprite touches the given color. */ - virtual bool touchingColor(const Value &color) const = 0; + virtual bool touchingColor(Rgb color) const = 0; /*! Used to check whether the mask part of the sprite touches the given color. */ - virtual bool touchingColor(const Value &color, const Value &mask) const = 0; + virtual bool touchingColor(Rgb color, Rgb mask) const = 0; }; } // namespace libscratchcpp diff --git a/include/scratchcpp/istacktimer.h b/include/scratchcpp/istacktimer.h new file mode 100644 index 00000000..c482042b --- /dev/null +++ b/include/scratchcpp/istacktimer.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "global.h" + +namespace libscratchcpp +{ + +/*! + * \brief The IStackTimer interface represents a timer that can be used by blocks. + * + * You can get a stack timer using ExecutionContext#stackTimer(). + */ +class LIBSCRATCHCPP_EXPORT IStackTimer +{ + public: + virtual ~IStackTimer() { } + + /*! Starts the timer. */ + virtual void start(double seconds) = 0; + + /*! Stops the timer. */ + virtual void stop() = 0; + + /*! Returns true if the timer has been stopped using stop() or wasn't used at all. */ + virtual bool stopped() const = 0; + + /*! Returns true if the timer has elapsed. */ + virtual bool elapsed() const = 0; +}; + +} // namespace libscratchcpp diff --git a/include/scratchcpp/istagehandler.h b/include/scratchcpp/istagehandler.h index b6cf7f50..b417ec32 100644 --- a/include/scratchcpp/istagehandler.h +++ b/include/scratchcpp/istagehandler.h @@ -62,10 +62,10 @@ class LIBSCRATCHCPP_EXPORT IStageHandler virtual bool touchingPoint(double x, double y) const = 0; /*! Used to check whether the stage touches the given color. */ - virtual bool touchingColor(const Value &color) const = 0; + virtual bool touchingColor(Rgb color) const = 0; /*! Used to check whether the mask part of the stage touches the given color. */ - virtual bool touchingColor(const Value &color, const Value &mask) const = 0; + virtual bool touchingColor(Rgb color, Rgb mask) const = 0; }; } // namespace libscratchcpp diff --git a/include/scratchcpp/list.h b/include/scratchcpp/list.h index 8ce30c85..d11d98e8 100644 --- a/include/scratchcpp/list.h +++ b/include/scratchcpp/list.h @@ -37,6 +37,21 @@ class LIBSCRATCHCPP_EXPORT List : public Entity Monitor *monitor() const; void setMonitor(Monitor *monitor); + /*! Returns a pointer to the raw list data. */ + inline ValueData *data() const { return m_dataPtr->data(); } + + /*! + * Returns a pointer to the list size. + * \note This is used internally by compiled code for various optimizations. + */ + inline size_t *sizePtr() { return &m_size; } + + /*! + * Returns a pointer to the allocated list size. + * \note This is used internally by compiled code for various optimizations. + */ + inline const size_t *allocatedSizePtr() const { return m_dataPtr->sizePtr(); } + /*! Returns the list size. */ inline size_t size() const { return m_size; } @@ -96,16 +111,19 @@ class LIBSCRATCHCPP_EXPORT List : public Entity m_size--; } - /*! Inserts an item at index. */ - inline void insert(size_t index, const ValueData &value) + /*! Inserts an empty item at index and returns the reference to it. Can be used for custom initialization. */ + inline ValueData &insertEmpty(size_t index) { assert(index >= 0 && index <= size()); m_size++; reserve(getAllocSize(m_size)); std::rotate(m_dataPtr->rbegin() + m_dataPtr->size() - m_size, m_dataPtr->rbegin() + m_dataPtr->size() - m_size + 1, m_dataPtr->rend() - index); - value_assign_copy(&m_dataPtr->operator[](index), &value); + return m_dataPtr->operator[](index); } + /*! Inserts an item at index. */ + inline void insert(size_t index, const ValueData &value) { value_assign_copy(&insertEmpty(index), &value); } + /*! Inserts an item at index. */ inline void insert(size_t index, const Value &value) { insert(index, value.data()); } @@ -139,7 +157,7 @@ class LIBSCRATCHCPP_EXPORT List : public Entity strings.push_back(std::string()); value_toString(item, &strings.back()); - if (value_isValidNumber(item) && !strings.back().empty()) { + if (value_isValidNumber(item) && !value_isBool(item) && !strings.back().empty()) { double doubleNum = value_toDouble(item); long num = value_toLong(item); diff --git a/include/scratchcpp/project.h b/include/scratchcpp/project.h index 6f1c455c..10a3e365 100644 --- a/include/scratchcpp/project.h +++ b/include/scratchcpp/project.h @@ -29,6 +29,7 @@ class LIBSCRATCHCPP_EXPORT Project Project(const Project &) = delete; bool load(); + void stopLoading(); void start(); void run(); diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h index 90f5abc7..b4906b1e 100644 --- a/include/scratchcpp/sound.h +++ b/include/scratchcpp/sound.h @@ -9,7 +9,6 @@ namespace libscratchcpp { -class Target; class SoundPrivate; /*! \brief The Sound class represents a Scratch sound. */ @@ -40,9 +39,6 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset virtual bool isPlaying() const; - Target *target() const; - void setTarget(Target *target); - std::shared_ptr clone() const; protected: diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h index f2f7d517..e62f50c5 100644 --- a/include/scratchcpp/sprite.h +++ b/include/scratchcpp/sprite.h @@ -81,8 +81,8 @@ class LIBSCRATCHCPP_EXPORT Sprite void keepInFence(double newX, double newY, double *fencedX, double *fencedY) const; bool touchingPoint(double x, double y) const override; - bool touchingColor(const Value &color) const override; - bool touchingColor(const Value &color, const Value &mask) const override; + bool touchingColor(Rgb color) const override; + bool touchingColor(Rgb color, Rgb mask) const override; void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override; diff --git a/include/scratchcpp/stage.h b/include/scratchcpp/stage.h index 3a0df955..274c56ba 100644 --- a/include/scratchcpp/stage.h +++ b/include/scratchcpp/stage.h @@ -55,8 +55,8 @@ class LIBSCRATCHCPP_EXPORT Stage : public Target Rect fastBoundingRect() const override; bool touchingPoint(double x, double y) const override; - bool touchingColor(const Value &color) const override; - bool touchingColor(const Value &color, const Value &mask) const override; + bool touchingColor(Rgb color) const override; + bool touchingColor(Rgb color, Rgb mask) const override; void setGraphicsEffectValue(IGraphicsEffect *effect, double value) override; diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h index 50c2ec71..523b70b9 100644 --- a/include/scratchcpp/target.h +++ b/include/scratchcpp/target.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include "drawable.h" #include "rect.h" @@ -28,7 +29,7 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable public: Target(); Target(const Target &) = delete; - virtual ~Target() { } + virtual ~Target(); bool isTarget() const override final; @@ -44,12 +45,16 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable int findVariable(const std::string &variableName) const; int findVariableById(const std::string &id) const; + ValueData **variableData(); + const std::vector> &lists() const; int addList(std::shared_ptr list); std::shared_ptr listAt(int index) const; int findList(const std::string &listName) const; int findListById(const std::string &id) const; + List **listData(); + const std::vector> &blocks() const; int addBlock(std::shared_ptr block); std::shared_ptr blockAt(int index) const; @@ -92,8 +97,8 @@ class LIBSCRATCHCPP_EXPORT Target : public Drawable bool touchingSprite(Sprite *sprite) const; virtual bool touchingPoint(double x, double y) const; bool touchingEdge() const; - virtual bool touchingColor(const Value &color) const; - virtual bool touchingColor(const Value &color, const Value &mask) const; + virtual bool touchingColor(Rgb color) const; + virtual bool touchingColor(Rgb color, Rgb mask) const; double graphicsEffectValue(IGraphicsEffect *effect) const; virtual void setGraphicsEffectValue(IGraphicsEffect *effect, double value); diff --git a/include/scratchcpp/thread.h b/include/scratchcpp/thread.h index f3f7693e..b5936db7 100644 --- a/include/scratchcpp/thread.h +++ b/include/scratchcpp/thread.h @@ -10,6 +10,9 @@ namespace libscratchcpp class VirtualMachine; class Target; +#ifdef USE_LLVM +class Promise; +#endif class IEngine; class Script; class ThreadPrivate; @@ -32,8 +35,13 @@ class LIBSCRATCHCPP_EXPORT Thread bool isFinished() const; +#ifdef USE_LLVM + std::shared_ptr promise() const; + void setPromise(std::shared_ptr promise); +#else void promise(); void resolvePromise(); +#endif private: spimpl::unique_impl_ptr impl; diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h index 96c781f3..7f0c6a54 100644 --- a/include/scratchcpp/value.h +++ b/include/scratchcpp/value.h @@ -139,6 +139,9 @@ class LIBSCRATCHCPP_EXPORT Value return ret; } + /*! Converts the value to an RGBA quadruplet. */ + Rgb toRgba() const { return value_toRgba(&m_data); } + /*! Adds the given value to the value. */ void add(const Value &v) { value_add(&m_data, &v.m_data, &m_data); } diff --git a/include/scratchcpp/value_functions.h b/include/scratchcpp/value_functions.h index d4409a0b..2a8a2217 100644 --- a/include/scratchcpp/value_functions.h +++ b/include/scratchcpp/value_functions.h @@ -5,6 +5,45 @@ namespace libscratchcpp { +/*! A typedef for unsigned int. Holds the RGBA values. */ +using Rgb = unsigned int; + +/*! Returns the red component of the quadruplet rgb. */ +inline constexpr int red(Rgb rgb) +{ + return ((rgb >> 16) & 0xff); +} + +/*! Returns the green component of the quadruplet rgb. */ +inline constexpr int green(Rgb rgb) +{ + return ((rgb >> 8) & 0xff); +} + +/*! Returns the blue component of the quadruplet rgb. */ +inline constexpr int blue(Rgb rgb) +{ + return (rgb & 0xff); +} + +/*! Returns the alpha component of the quadruplet rgb. */ +inline constexpr int alpha(Rgb rgb) +{ + return rgb >> 24; +} + +/*! Creates an RGB triplet from the given color components. */ +inline constexpr Rgb rgb(int r, int g, int b) +{ + return (0xffu << 24) | ((r & 0xffu) << 16) | ((g & 0xffu) << 8) | (b & 0xffu); +} + +/*! Creates an RGBA quadruplet from the given color components. */ +inline constexpr Rgb rgba(int r, int g, int b, int a) +{ + return ((a & 0xffu) << 24) | ((r & 0xffu) << 16) | ((g & 0xffu) << 8) | (b & 0xffu); +} + extern "C" { LIBSCRATCHCPP_EXPORT void value_free(ValueData *v); @@ -33,6 +72,9 @@ extern "C" LIBSCRATCHCPP_EXPORT void value_toString(const ValueData *v, std::string *dst); LIBSCRATCHCPP_EXPORT char *value_toCString(const ValueData *v); LIBSCRATCHCPP_EXPORT void value_toUtf16(const ValueData *v, std::u16string *dst); + LIBSCRATCHCPP_EXPORT Rgb value_toRgba(const ValueData *v); + + LIBSCRATCHCPP_EXPORT bool value_doubleIsInt(double v); LIBSCRATCHCPP_EXPORT char *value_doubleToCString(double v); LIBSCRATCHCPP_EXPORT const char *value_boolToCString(bool v); diff --git a/include/scratchcpp/veque.h b/include/scratchcpp/veque.h index f75877c7..a994ebc6 100644 --- a/include/scratchcpp/veque.h +++ b/include/scratchcpp/veque.h @@ -375,6 +375,11 @@ namespace veque return _size; } + // For libscratchcpp List + inline const size_type *sizePtr() const noexcept { + return &_size; + } + size_type max_size() const noexcept { constexpr auto compile_time_limit = std::min( diff --git a/src/audio/internal/thirdparty/miniaudio/miniaudio.c b/src/audio/internal/thirdparty/miniaudio/miniaudio.c index 651843b2..077c396c 100644 --- a/src/audio/internal/thirdparty/miniaudio/miniaudio.c +++ b/src/audio/internal/thirdparty/miniaudio/miniaudio.c @@ -18616,15 +18616,20 @@ static ma_pa_buffer_attr ma_device__pa_buffer_attr_new(ma_uint32 periodSizeInFra static ma_pa_stream* ma_device__pa_stream_new__pulse(ma_device* pDevice, const char* pStreamName, const ma_pa_sample_spec* ss, const ma_pa_channel_map* cmap) { static int g_StreamCounter = 0; + static ma_mutex g_StreamCounterMutex; char actualStreamName[256]; - if (pStreamName != NULL) { - ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); - } else { - ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); - ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ + ma_mutex_lock(&g_StreamCounterMutex); + { + if (pStreamName != NULL) { + ma_strncpy_s(actualStreamName, sizeof(actualStreamName), pStreamName, (size_t)-1); + } else { + ma_strcpy_s(actualStreamName, sizeof(actualStreamName), "miniaudio:"); + ma_itoa_s(g_StreamCounter, actualStreamName + 8, sizeof(actualStreamName)-8, 10); /* 8 = strlen("miniaudio:") */ + } + g_StreamCounter += 1; } - g_StreamCounter += 1; + ma_mutex_unlock(&g_StreamCounterMutex); return ((ma_pa_stream_new_proc)pDevice->pContext->pulse.pa_stream_new)((ma_pa_context*)pDevice->pulse.pPulseContext, actualStreamName, ss, cmap); } diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp index ab5aab69..aa4ebbce 100644 --- a/src/blocks/controlblocks.cpp +++ b/src/blocks/controlblocks.cpp @@ -25,6 +25,11 @@ std::string ControlBlocks::description() const return name() + " blocks"; } +Rgb ControlBlocks::color() const +{ + return rgb(255, 171, 25); +} + void ControlBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/controlblocks.h b/src/blocks/controlblocks.h index aa0c8710..623ab651 100644 --- a/src/blocks/controlblocks.h +++ b/src/blocks/controlblocks.h @@ -43,6 +43,7 @@ class ControlBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/customblocks.cpp b/src/blocks/customblocks.cpp index aee3b757..8066a025 100644 --- a/src/blocks/customblocks.cpp +++ b/src/blocks/customblocks.cpp @@ -20,6 +20,11 @@ std::string CustomBlocks::description() const return name(); } +Rgb CustomBlocks::color() const +{ + return rgb(255, 102, 128); +} + void CustomBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/customblocks.h b/src/blocks/customblocks.h index a40e37c3..23482886 100644 --- a/src/blocks/customblocks.h +++ b/src/blocks/customblocks.h @@ -24,6 +24,7 @@ class CustomBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp index 757e2bda..85ec9235 100644 --- a/src/blocks/eventblocks.cpp +++ b/src/blocks/eventblocks.cpp @@ -30,6 +30,11 @@ std::string libscratchcpp::EventBlocks::description() const return "Event blocks"; } +Rgb EventBlocks::color() const +{ + return rgb(255, 191, 0); +} + void EventBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/eventblocks.h b/src/blocks/eventblocks.h index e4d28f4a..9ddca5b6 100644 --- a/src/blocks/eventblocks.h +++ b/src/blocks/eventblocks.h @@ -38,6 +38,7 @@ class EventBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/listblocks.cpp b/src/blocks/listblocks.cpp index 135dd0bd..4302c3bc 100644 --- a/src/blocks/listblocks.cpp +++ b/src/blocks/listblocks.cpp @@ -23,6 +23,11 @@ std::string ListBlocks::description() const return "List blocks"; } +Rgb ListBlocks::color() const +{ + return rgb(255, 102, 26); +} + void ListBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/listblocks.h b/src/blocks/listblocks.h index 69e49aa3..ed621314 100644 --- a/src/blocks/listblocks.h +++ b/src/blocks/listblocks.h @@ -28,6 +28,7 @@ class ListBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/looksblocks.cpp b/src/blocks/looksblocks.cpp index f142f34c..4a5ddf31 100644 --- a/src/blocks/looksblocks.cpp +++ b/src/blocks/looksblocks.cpp @@ -42,6 +42,11 @@ std::string LooksBlocks::description() const return name() + " blocks"; } +Rgb LooksBlocks::color() const +{ + return rgb(153, 102, 255); +} + void LooksBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/looksblocks.h b/src/blocks/looksblocks.h index 8881d7eb..eca733de 100644 --- a/src/blocks/looksblocks.h +++ b/src/blocks/looksblocks.h @@ -60,6 +60,7 @@ class LooksBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/motionblocks.cpp b/src/blocks/motionblocks.cpp index c5a96004..62fd28ae 100644 --- a/src/blocks/motionblocks.cpp +++ b/src/blocks/motionblocks.cpp @@ -29,6 +29,11 @@ std::string MotionBlocks::description() const return name() + " blocks"; } +Rgb MotionBlocks::color() const +{ + return rgb(76, 151, 255); +} + void MotionBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/motionblocks.h b/src/blocks/motionblocks.h index b545822e..b86e9481 100644 --- a/src/blocks/motionblocks.h +++ b/src/blocks/motionblocks.h @@ -45,6 +45,7 @@ class MotionBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/operatorblocks.cpp b/src/blocks/operatorblocks.cpp index 376ae27a..567d4a15 100644 --- a/src/blocks/operatorblocks.cpp +++ b/src/blocks/operatorblocks.cpp @@ -18,6 +18,11 @@ std::string OperatorBlocks::description() const return "Operator blocks"; } +Rgb OperatorBlocks::color() const +{ + return rgb(89, 192, 89); +} + void OperatorBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/operatorblocks.h b/src/blocks/operatorblocks.h index e9dd932a..f2f5b729 100644 --- a/src/blocks/operatorblocks.h +++ b/src/blocks/operatorblocks.h @@ -55,6 +55,7 @@ class OperatorBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp index a1ef1c55..f5b18ce7 100644 --- a/src/blocks/sensingblocks.cpp +++ b/src/blocks/sensingblocks.cpp @@ -34,6 +34,11 @@ std::string SensingBlocks::description() const return name() + " blocks"; } +Rgb SensingBlocks::color() const +{ + return rgb(92, 177, 214); +} + void SensingBlocks::registerBlocks(IEngine *engine) { // Blocks @@ -538,13 +543,13 @@ unsigned int SensingBlocks::touchingEdge(VirtualMachine *vm) unsigned int SensingBlocks::touchingColor(VirtualMachine *vm) { - vm->replaceReturnValue(vm->target()->touchingColor(*vm->getInput(0, 1)), 1); + vm->replaceReturnValue(vm->target()->touchingColor(vm->getInput(0, 1)->toRgba()), 1); return 0; } unsigned int SensingBlocks::colorIsTouchingColor(VirtualMachine *vm) { - vm->replaceReturnValue(vm->target()->touchingColor(*vm->getInput(0, 2), *vm->getInput(1, 2)), 2); + vm->replaceReturnValue(vm->target()->touchingColor(vm->getInput(0, 2)->toRgba(), vm->getInput(1, 2)->toRgba()), 2); return 1; } diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h index 8153a18b..5b3fc638 100644 --- a/src/blocks/sensingblocks.h +++ b/src/blocks/sensingblocks.h @@ -60,6 +60,7 @@ class SensingBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/soundblocks.cpp b/src/blocks/soundblocks.cpp index 6cc2aa7b..f001fa7b 100644 --- a/src/blocks/soundblocks.cpp +++ b/src/blocks/soundblocks.cpp @@ -34,6 +34,11 @@ std::string SoundBlocks::description() const return name() + " blocks"; } +Rgb SoundBlocks::color() const +{ + return rgb(207, 99, 207); +} + void SoundBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/soundblocks.h b/src/blocks/soundblocks.h index d31b8a62..0535eb73 100644 --- a/src/blocks/soundblocks.h +++ b/src/blocks/soundblocks.h @@ -37,6 +37,7 @@ class SoundBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; void onInit(IEngine *engine) override; diff --git a/src/blocks/variableblocks.cpp b/src/blocks/variableblocks.cpp index 81961e49..17236a30 100644 --- a/src/blocks/variableblocks.cpp +++ b/src/blocks/variableblocks.cpp @@ -23,6 +23,11 @@ std::string VariableBlocks::description() const return "Variable blocks"; } +Rgb VariableBlocks::color() const +{ + return rgb(255, 140, 26); +} + void VariableBlocks::registerBlocks(IEngine *engine) { // Blocks diff --git a/src/blocks/variableblocks.h b/src/blocks/variableblocks.h index 2372660b..f4b2922c 100644 --- a/src/blocks/variableblocks.h +++ b/src/blocks/variableblocks.h @@ -27,6 +27,7 @@ class VariableBlocks : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/src/dev/CMakeLists.txt b/src/dev/CMakeLists.txt index 696178cd..4c9b3cd6 100644 --- a/src/dev/CMakeLists.txt +++ b/src/dev/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(blocks) add_subdirectory(engine) +add_subdirectory(test) diff --git a/src/dev/blocks/controlblocks.cpp b/src/dev/blocks/controlblocks.cpp index 937e5943..0ca0524d 100644 --- a/src/dev/blocks/controlblocks.cpp +++ b/src/dev/blocks/controlblocks.cpp @@ -1,5 +1,17 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "controlblocks.h" using namespace libscratchcpp; @@ -14,6 +26,213 @@ std::string ControlBlocks::description() const return name() + " blocks"; } +Rgb ControlBlocks::color() const +{ + return rgb(255, 171, 25); +} + void ControlBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "control_forever", &compileForever); + engine->addCompileFunction(this, "control_repeat", &compileRepeat); + engine->addCompileFunction(this, "control_if", &compileIf); + engine->addCompileFunction(this, "control_if_else", &compileIfElse); + engine->addCompileFunction(this, "control_stop", &compileStop); + engine->addCompileFunction(this, "control_wait", &compileWait); + engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil); + engine->addCompileFunction(this, "control_repeat_until", &compileRepeatUntil); + engine->addCompileFunction(this, "control_while", &compileWhile); + engine->addCompileFunction(this, "control_for_each", &compileForEach); + engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone); + engine->addCompileFunction(this, "control_create_clone_of", &compileCreateCloneOf); + engine->addCompileFunction(this, "control_delete_this_clone", &compileDeleteThisClone); +} + +CompilerValue *ControlBlocks::compileForever(Compiler *compiler) +{ + auto substack = compiler->input("SUBSTACK"); + compiler->beginLoopCondition(); + compiler->moveToWhileLoop(compiler->addConstValue(true), substack ? substack->valueBlock() : nullptr); + return nullptr; +} + +CompilerValue *ControlBlocks::compileRepeat(Compiler *compiler) +{ + auto substack = compiler->input("SUBSTACK"); + compiler->moveToRepeatLoop(compiler->addInput("TIMES"), substack ? substack->valueBlock() : nullptr); + return nullptr; +} + +CompilerValue *ControlBlocks::compileIf(Compiler *compiler) +{ + auto substack = compiler->input("SUBSTACK"); + compiler->moveToIf(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr); + return nullptr; +} + +CompilerValue *ControlBlocks::compileIfElse(Compiler *compiler) +{ + auto substack = compiler->input("SUBSTACK"); + auto substack2 = compiler->input("SUBSTACK2"); + compiler->moveToIfElse(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr, substack2 ? substack2->valueBlock() : nullptr); + return nullptr; +} + +CompilerValue *ControlBlocks::compileStop(Compiler *compiler) +{ + Field *option = compiler->field("STOP_OPTION"); + + if (option) { + std::string str = option->value().toString(); + + if (str == "all") + compiler->addFunctionCallWithCtx("control_stop_all", Compiler::StaticType::Void); + else if (str == "this script") + compiler->createStop(); + else if (str == "other scripts in sprite" || str == "other scripts in stage") + compiler->addFunctionCallWithCtx("control_stop_other_scripts_in_target", Compiler::StaticType::Void); + } + + return nullptr; +} + +CompilerValue *ControlBlocks::compileWait(Compiler *compiler) +{ + auto duration = compiler->addInput("DURATION"); + compiler->addFunctionCallWithCtx("control_start_wait", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { duration }); + compiler->createYield(); + + compiler->beginLoopCondition(); + auto elapsed = compiler->addFunctionCallWithCtx("control_stack_timer_elapsed", Compiler::StaticType::Bool); + compiler->beginRepeatUntilLoop(elapsed); + compiler->endLoop(); + + return nullptr; +} + +CompilerValue *ControlBlocks::compileWaitUntil(Compiler *compiler) +{ + compiler->beginLoopCondition(); + compiler->beginRepeatUntilLoop(compiler->addInput("CONDITION")); + compiler->endLoop(); + return nullptr; +} + +CompilerValue *ControlBlocks::compileRepeatUntil(Compiler *compiler) +{ + auto substack = compiler->input("SUBSTACK"); + compiler->beginLoopCondition(); + compiler->moveToRepeatUntilLoop(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr); + return nullptr; +} + +CompilerValue *ControlBlocks::compileWhile(Compiler *compiler) +{ + auto substack = compiler->input("SUBSTACK"); + compiler->beginLoopCondition(); + compiler->moveToWhileLoop(compiler->addInput("CONDITION"), substack ? substack->valueBlock() : nullptr); + return nullptr; +} + +CompilerValue *ControlBlocks::compileForEach(Compiler *compiler) +{ + Variable *var = static_cast(compiler->field("VARIABLE")->valuePtr().get()); + assert(var); + auto substack = compiler->input("SUBSTACK"); + compiler->moveToRepeatLoop(compiler->addInput("VALUE"), substack ? substack->valueBlock() : nullptr); + auto index = compiler->createAdd(compiler->addLoopIndex(), compiler->addConstValue(1)); + compiler->createVariableWrite(var, index); + return nullptr; +} + +CompilerValue *ControlBlocks::compileStartAsClone(Compiler *compiler) +{ + compiler->engine()->addCloneInitScript(compiler->block()); + return nullptr; +} + +CompilerValue *ControlBlocks::compileCreateCloneOf(Compiler *compiler) +{ + Input *input = compiler->input("CLONE_OPTION"); + + if (input->pointsToDropdownMenu()) { + std::string spriteName = input->selectedMenuItem(); + + if (spriteName == "_myself_") + compiler->addTargetFunctionCall("control_create_clone_of_myself"); + else { + auto index = compiler->engine()->findTarget(spriteName); + CompilerValue *arg = compiler->addConstValue(index); + compiler->addFunctionCallWithCtx("control_create_clone_by_index", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { arg }); + } + } else { + CompilerValue *arg = compiler->addInput("CLONE_OPTION"); + compiler->addFunctionCallWithCtx("control_create_clone", Compiler::StaticType::Void, { Compiler::StaticType::String }, { arg }); + } + + return nullptr; +} + +CompilerValue *ControlBlocks::compileDeleteThisClone(Compiler *compiler) +{ + compiler->addTargetFunctionCall("control_delete_this_clone"); + return nullptr; +} + +extern "C" void control_stop_all(ExecutionContext *ctx) +{ + ctx->engine()->stop(); +} + +extern "C" void control_stop_other_scripts_in_target(ExecutionContext *ctx) +{ + Thread *thread = ctx->thread(); + ctx->engine()->stopTarget(thread->target(), thread); +} + +extern "C" void control_start_wait(ExecutionContext *ctx, double seconds) +{ + ctx->stackTimer()->start(seconds); + ctx->engine()->requestRedraw(); +} + +extern "C" bool control_stack_timer_elapsed(ExecutionContext *ctx) +{ + return ctx->stackTimer()->elapsed(); +} + +extern "C" void control_create_clone_of_myself(Target *target) +{ + if (!target->isStage()) + static_cast(target)->clone(); +} + +extern "C" void control_create_clone_by_index(ExecutionContext *ctx, double index) +{ + Target *target = ctx->engine()->targetAt(index); + + if (!target->isStage()) + static_cast(target)->clone(); +} + +extern "C" void control_create_clone(ExecutionContext *ctx, const char *spriteName) +{ + if (strcmp(spriteName, "_myself_") == 0) + control_create_clone_of_myself(ctx->thread()->target()); + else { + IEngine *engine = ctx->engine(); + auto index = engine->findTarget(spriteName); + Target *target = engine->targetAt(index); + + if (!target->isStage()) + static_cast(target)->clone(); + } +} + +extern "C" void control_delete_this_clone(Target *target) +{ + if (!target->isStage()) { + target->engine()->stopTarget(target, nullptr); + static_cast(target)->deleteClone(); + } } diff --git a/src/dev/blocks/controlblocks.h b/src/dev/blocks/controlblocks.h index 155f9296..0f44982c 100644 --- a/src/dev/blocks/controlblocks.h +++ b/src/dev/blocks/controlblocks.h @@ -12,8 +12,24 @@ class ControlBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileForever(Compiler *compiler); + static CompilerValue *compileRepeat(Compiler *compiler); + static CompilerValue *compileIf(Compiler *compiler); + static CompilerValue *compileIfElse(Compiler *compiler); + static CompilerValue *compileStop(Compiler *compiler); + static CompilerValue *compileWait(Compiler *compiler); + static CompilerValue *compileWaitUntil(Compiler *compiler); + static CompilerValue *compileRepeatUntil(Compiler *compiler); + static CompilerValue *compileWhile(Compiler *compiler); + static CompilerValue *compileForEach(Compiler *compiler); + static CompilerValue *compileStartAsClone(Compiler *compiler); + static CompilerValue *compileCreateCloneOf(Compiler *compiler); + static CompilerValue *compileDeleteThisClone(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/src/dev/blocks/customblocks.cpp b/src/dev/blocks/customblocks.cpp index 4a1657b3..655e4d3b 100644 --- a/src/dev/blocks/customblocks.cpp +++ b/src/dev/blocks/customblocks.cpp @@ -1,5 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include + #include "customblocks.h" using namespace libscratchcpp; @@ -14,6 +20,41 @@ std::string CustomBlocks::description() const return name(); } +Rgb CustomBlocks::color() const +{ + return rgb(255, 102, 128); +} + 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 &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); } diff --git a/src/dev/blocks/customblocks.h b/src/dev/blocks/customblocks.h index 433afa36..b2a2613f 100644 --- a/src/dev/blocks/customblocks.h +++ b/src/dev/blocks/customblocks.h @@ -12,8 +12,13 @@ class CustomBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileCall(Compiler *compiler); + static CompilerValue *compileArgument(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/src/dev/blocks/eventblocks.cpp b/src/dev/blocks/eventblocks.cpp index 07477984..ba4d2663 100644 --- a/src/dev/blocks/eventblocks.cpp +++ b/src/dev/blocks/eventblocks.cpp @@ -1,5 +1,15 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "eventblocks.h" using namespace libscratchcpp; @@ -14,6 +24,116 @@ std::string libscratchcpp::EventBlocks::description() const return "Event blocks"; } +Rgb EventBlocks::color() const +{ + return rgb(255, 191, 0); +} + void EventBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "event_whentouchingobject", &compileWhenTouchingObject); + engine->addCompileFunction(this, "event_whenflagclicked", &compileWhenFlagClicked); + engine->addCompileFunction(this, "event_whenthisspriteclicked", &compileWhenThisSpriteClicked); + engine->addCompileFunction(this, "event_whenstageclicked", &compileWhenStageClicked); + engine->addCompileFunction(this, "event_whenbroadcastreceived", &compileWhenBroadcastReceived); + engine->addCompileFunction(this, "event_whenbackdropswitchesto", &compileWhenBackdropSwitchesTo); + engine->addCompileFunction(this, "event_whengreaterthan", &compileWhenGreaterThan); + engine->addCompileFunction(this, "event_broadcast", &compileBroadcast); + engine->addCompileFunction(this, "event_broadcastandwait", &compileBroadcastAndWait); + engine->addCompileFunction(this, "event_whenkeypressed", &compileWhenKeyPressed); +} + +CompilerValue *EventBlocks::compileWhenTouchingObject(Compiler *compiler) +{ + compiler->engine()->addWhenTouchingObjectScript(compiler->block()); + return nullptr; +} + +CompilerValue *EventBlocks::compileWhenFlagClicked(Compiler *compiler) +{ + compiler->engine()->addGreenFlagScript(compiler->block()); + return nullptr; +} + +CompilerValue *EventBlocks::compileWhenThisSpriteClicked(Compiler *compiler) +{ + compiler->engine()->addTargetClickScript(compiler->block()); + return nullptr; +} + +CompilerValue *EventBlocks::compileWhenStageClicked(Compiler *compiler) +{ + compiler->engine()->addTargetClickScript(compiler->block()); + return nullptr; +} + +CompilerValue *EventBlocks::compileWhenBroadcastReceived(Compiler *compiler) +{ + auto block = compiler->block(); + Field *field = compiler->field("BROADCAST_OPTION"); + + if (field) { + auto broadcast = std::static_pointer_cast(field->valuePtr()); + compiler->engine()->addBroadcastScript(block, field, broadcast.get()); + } + + return nullptr; +} + +CompilerValue *EventBlocks::compileWhenBackdropSwitchesTo(Compiler *compiler) +{ + auto block = compiler->block(); + Field *field = compiler->field("BACKDROP"); + + if (field) + compiler->engine()->addBackdropChangeScript(block, field); + + return nullptr; +} + +CompilerValue *EventBlocks::compileWhenGreaterThan(Compiler *compiler) +{ + compiler->engine()->addWhenGreaterThanScript(compiler->block()); + return nullptr; +} + +CompilerValue *EventBlocks::compileBroadcast(Compiler *compiler) +{ + auto input = compiler->addInput("BROADCAST_INPUT"); + auto wait = compiler->addConstValue(false); + compiler->addFunctionCallWithCtx("event_broadcast", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { input, wait }); + return nullptr; +} + +CompilerValue *EventBlocks::compileBroadcastAndWait(Compiler *compiler) +{ + auto input = compiler->addInput("BROADCAST_INPUT"); + auto wait = compiler->addConstValue(true); + compiler->addFunctionCallWithCtx("event_broadcast", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::Bool }, { input, wait }); + compiler->createYield(); + return nullptr; +} + +CompilerValue *EventBlocks::compileWhenKeyPressed(Compiler *compiler) +{ + auto block = compiler->block(); + Field *field = compiler->field("KEY_OPTION"); + + if (field) + compiler->engine()->addKeyPressScript(block, field); + + return nullptr; +} + +extern "C" void event_broadcast(ExecutionContext *ctx, const char *name, bool wait) +{ + Thread *thread = ctx->thread(); + IEngine *engine = thread->engine(); + std::vector broadcasts = engine->findBroadcasts(name); + + for (int index : broadcasts) + engine->broadcast(index, thread, wait); + + if (wait) + ctx->setPromise(std::make_shared()); } diff --git a/src/dev/blocks/eventblocks.h b/src/dev/blocks/eventblocks.h index c395c3de..512bae0c 100644 --- a/src/dev/blocks/eventblocks.h +++ b/src/dev/blocks/eventblocks.h @@ -12,8 +12,21 @@ class EventBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileWhenTouchingObject(Compiler *compiler); + static CompilerValue *compileWhenFlagClicked(Compiler *compiler); + static CompilerValue *compileWhenThisSpriteClicked(Compiler *compiler); + static CompilerValue *compileWhenStageClicked(Compiler *compiler); + static CompilerValue *compileWhenBroadcastReceived(Compiler *compiler); + static CompilerValue *compileWhenBackdropSwitchesTo(Compiler *compiler); + static CompilerValue *compileWhenGreaterThan(Compiler *compiler); + static CompilerValue *compileBroadcast(Compiler *compiler); + static CompilerValue *compileBroadcastAndWait(Compiler *compiler); + static CompilerValue *compileWhenKeyPressed(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/src/dev/blocks/listblocks.cpp b/src/dev/blocks/listblocks.cpp index 8b4602ad..64b44135 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; @@ -14,6 +20,177 @@ std::string ListBlocks::description() const return "List blocks"; } +Rgb ListBlocks::color() const +{ + return rgb(255, 102, 26); +} + 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->createStrCmpEQ(input, compiler->addConstValue("random"), true); + CompilerValue *isRandom2 = compiler->createStrCmpEQ(input, compiler->addConstValue("any"), true); + 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->createStrCmpEQ(input, compiler->addConstValue("last"), true); + 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->createStrCmpEQ(index, compiler->addConstValue("all"), true); + compiler->beginIfStatement(cond); + { + compiler->createListClear(list); + } + compiler->beginElseBranch(); + { + CompilerValue *max = compiler->addListSize(list); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + compiler->createListRemove(list, index); + } + 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 *max = compiler->createAdd(compiler->addListSize(list), compiler->addConstValue(1)); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + CompilerValue *item = compiler->addInput("ITEM"); + compiler->createListInsert(list, index, item); + } + + 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 *max = compiler->addListSize(list); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + CompilerValue *item = compiler->addInput("ITEM"); + compiler->createListReplace(list, index, item); + } + + 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 *max = compiler->addListSize(list); + index = getListIndex(compiler, index, list, max); + index = compiler->createSub(index, compiler->addConstValue(1)); + return compiler->addListItem(list, index); + } + + 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..02dbd79c 100644 --- a/src/dev/blocks/listblocks.h +++ b/src/dev/blocks/listblocks.h @@ -7,13 +7,28 @@ namespace libscratchcpp { +class List; + class ListBlocks : public IExtension { public: std::string name() const override; std::string description() const override; + Rgb color() 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/blocks/looksblocks.cpp b/src/dev/blocks/looksblocks.cpp index 0d471fb0..8da59e7a 100644 --- a/src/dev/blocks/looksblocks.cpp +++ b/src/dev/blocks/looksblocks.cpp @@ -14,6 +14,11 @@ std::string LooksBlocks::description() const return name() + " blocks"; } +Rgb LooksBlocks::color() const +{ + return rgb(153, 102, 255); +} + void LooksBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/looksblocks.h b/src/dev/blocks/looksblocks.h index 8bfa5f18..b7529754 100644 --- a/src/dev/blocks/looksblocks.h +++ b/src/dev/blocks/looksblocks.h @@ -12,6 +12,7 @@ class LooksBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/motionblocks.cpp b/src/dev/blocks/motionblocks.cpp index e44a673c..a249cdff 100644 --- a/src/dev/blocks/motionblocks.cpp +++ b/src/dev/blocks/motionblocks.cpp @@ -14,6 +14,11 @@ std::string MotionBlocks::description() const return name() + " blocks"; } +Rgb MotionBlocks::color() const +{ + return rgb(76, 151, 255); +} + void MotionBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/motionblocks.h b/src/dev/blocks/motionblocks.h index 125b4073..44d7c0bd 100644 --- a/src/dev/blocks/motionblocks.h +++ b/src/dev/blocks/motionblocks.h @@ -12,6 +12,7 @@ class MotionBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/operatorblocks.cpp b/src/dev/blocks/operatorblocks.cpp index 8d4cbb92..508d41a1 100644 --- a/src/dev/blocks/operatorblocks.cpp +++ b/src/dev/blocks/operatorblocks.cpp @@ -1,5 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include + #include "operatorblocks.h" using namespace libscratchcpp; @@ -14,6 +21,213 @@ std::string OperatorBlocks::description() const return "Operator blocks"; } +Rgb OperatorBlocks::color() const +{ + return rgb(89, 192, 89); +} + void OperatorBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "operator_add", &compileAdd); + engine->addCompileFunction(this, "operator_subtract", &compileSubtract); + engine->addCompileFunction(this, "operator_multiply", &compileMultiply); + engine->addCompileFunction(this, "operator_divide", &compileDivide); + engine->addCompileFunction(this, "operator_random", &compileRandom); + engine->addCompileFunction(this, "operator_lt", &compileLt); + engine->addCompileFunction(this, "operator_equals", &compileEquals); + engine->addCompileFunction(this, "operator_gt", &compileGt); + engine->addCompileFunction(this, "operator_and", &compileAnd); + engine->addCompileFunction(this, "operator_or", &compileOr); + engine->addCompileFunction(this, "operator_not", &compileNot); + engine->addCompileFunction(this, "operator_join", &compileJoin); + engine->addCompileFunction(this, "operator_letter_of", &compileLetterOf); + engine->addCompileFunction(this, "operator_length", &compileLength); + engine->addCompileFunction(this, "operator_contains", &compileContains); + engine->addCompileFunction(this, "operator_mod", &compileMod); + engine->addCompileFunction(this, "operator_round", &compileRound); + engine->addCompileFunction(this, "operator_mathop", &compileMathOp); +} + +CompilerValue *OperatorBlocks::compileAdd(Compiler *compiler) +{ + return compiler->createAdd(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} + +CompilerValue *OperatorBlocks::compileSubtract(Compiler *compiler) +{ + return compiler->createSub(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} + +CompilerValue *OperatorBlocks::compileMultiply(Compiler *compiler) +{ + return compiler->createMul(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} + +CompilerValue *OperatorBlocks::compileDivide(Compiler *compiler) +{ + return compiler->createDiv(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} + +CompilerValue *OperatorBlocks::compileRandom(Compiler *compiler) +{ + auto from = compiler->addInput("FROM"); + auto to = compiler->addInput("TO"); + return compiler->createRandom(from, to); +} + +CompilerValue *OperatorBlocks::compileLt(Compiler *compiler) +{ + return compiler->createCmpLT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} + +CompilerValue *OperatorBlocks::compileEquals(Compiler *compiler) +{ + return compiler->createCmpEQ(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} + +CompilerValue *OperatorBlocks::compileGt(Compiler *compiler) +{ + return compiler->createCmpGT(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} + +CompilerValue *OperatorBlocks::compileAnd(Compiler *compiler) +{ + return compiler->createAnd(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} + +CompilerValue *OperatorBlocks::compileOr(Compiler *compiler) +{ + return compiler->createOr(compiler->addInput("OPERAND1"), compiler->addInput("OPERAND2")); +} + +CompilerValue *OperatorBlocks::compileNot(Compiler *compiler) +{ + return compiler->createNot(compiler->addInput("OPERAND")); +} + +CompilerValue *OperatorBlocks::compileJoin(Compiler *compiler) +{ + auto string1 = compiler->addInput("STRING1"); + auto string2 = compiler->addInput("STRING2"); + return compiler->addFunctionCall("operator_join", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 }); +} + +CompilerValue *OperatorBlocks::compileLetterOf(Compiler *compiler) +{ + auto letter = compiler->addInput("LETTER"); + auto string = compiler->addInput("STRING"); + return compiler->addFunctionCall("operator_letter_of", Compiler::StaticType::String, { Compiler::StaticType::Number, Compiler::StaticType::String }, { letter, string }); +} + +CompilerValue *OperatorBlocks::compileLength(Compiler *compiler) +{ + auto string = compiler->addInput("STRING"); + return compiler->addFunctionCall("operator_length", Compiler::StaticType::Number, { Compiler::StaticType::String }, { string }); +} + +CompilerValue *OperatorBlocks::compileContains(Compiler *compiler) +{ + auto string1 = compiler->addInput("STRING1"); + auto string2 = compiler->addInput("STRING2"); + return compiler->addFunctionCall("operator_contains", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { string1, string2 }); +} + +CompilerValue *OperatorBlocks::compileMod(Compiler *compiler) +{ + return compiler->createMod(compiler->addInput("NUM1"), compiler->addInput("NUM2")); +} + +CompilerValue *OperatorBlocks::compileRound(Compiler *compiler) +{ + return compiler->createRound(compiler->addInput("NUM")); +} + +CompilerValue *OperatorBlocks::compileMathOp(Compiler *compiler) +{ + Field *opField = compiler->field("OPERATOR"); + const std::string numInput = "NUM"; + const std::string &op = opField->value().toString(); + + if (op == "abs") + return compiler->createAbs(compiler->addInput(numInput)); + else if (op == "floor") + return compiler->createFloor(compiler->addInput(numInput)); + else if (op == "ceiling") + return compiler->createCeil(compiler->addInput(numInput)); + else if (op == "sqrt") + return compiler->createSqrt(compiler->addInput(numInput)); + else if (op == "sin") + return compiler->createSin(compiler->addInput(numInput)); + else if (op == "cos") + return compiler->createCos(compiler->addInput(numInput)); + else if (op == "tan") + return compiler->createTan(compiler->addInput(numInput)); + else if (op == "asin") + return compiler->createAsin(compiler->addInput(numInput)); + else if (op == "acos") + return compiler->createAcos(compiler->addInput(numInput)); + else if (op == "atan") + return compiler->createAtan(compiler->addInput(numInput)); + else if (op == "ln") + return compiler->createLn(compiler->addInput(numInput)); + else if (op == "log") + return compiler->createLog10(compiler->addInput(numInput)); + else if (op == "e ^") + return compiler->createExp(compiler->addInput(numInput)); + else if (op == "10 ^") + return compiler->createExp10(compiler->addInput(numInput)); + else + return compiler->addConstValue(Value()); +} + +extern "C" char *operator_join(const char *string1, const char *string2) +{ + const size_t len1 = strlen(string1); + const size_t len2 = strlen(string2); + + char *ret = (char *)malloc((len1 + len2 + 1) * sizeof(char)); + size_t i; + + for (i = 0; i < len1; i++) + ret[i] = string1[i]; + + for (i = 0; i < len2 + 1; i++) // +1: null-terminate + ret[len1 + i] = string2[i]; + + return ret; +} + +extern "C" char *operator_letter_of(double letter, const char *string) +{ + const size_t len = strlen(string); + + if (letter < 1 || letter > len) { + char *ret = (char *)malloc(sizeof(char)); + ret[0] = '\0'; + return ret; + } + + // TODO: Rewrite this + std::u16string u16 = utf8::utf8to16(std::string(string)); + std::string str = utf8::utf16to8(std::u16string({ u16[(size_t)letter - 1] })); + char *ret = (char *)malloc((str.size() + 1) * sizeof(char)); + strcpy(ret, str.c_str()); + + return ret; +} + +extern "C" double operator_length(const char *string) +{ + // TODO: Rewrite this + return utf8::utf8to16(std::string(string)).size(); +} + +extern "C" bool operator_contains(const char *string1, const char *string2) +{ + // TODO: Rewrite this + std::u16string u16string1 = utf8::utf8to16(std::string(string1)); + std::u16string u16string2 = utf8::utf8to16(std::string(string2)); + std::transform(u16string1.begin(), u16string1.end(), u16string1.begin(), ::tolower); + std::transform(u16string2.begin(), u16string2.end(), u16string2.begin(), ::tolower); + return (u16string1.find(u16string2) != std::u16string::npos); } diff --git a/src/dev/blocks/operatorblocks.h b/src/dev/blocks/operatorblocks.h index af5bd7b5..647512e6 100644 --- a/src/dev/blocks/operatorblocks.h +++ b/src/dev/blocks/operatorblocks.h @@ -7,13 +7,36 @@ namespace libscratchcpp { +class IRandomGenerator; + class OperatorBlocks : public IExtension { public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileAdd(Compiler *compiler); + static CompilerValue *compileSubtract(Compiler *compiler); + static CompilerValue *compileMultiply(Compiler *compiler); + static CompilerValue *compileDivide(Compiler *compiler); + static CompilerValue *compileRandom(Compiler *compiler); + static CompilerValue *compileLt(Compiler *compiler); + static CompilerValue *compileEquals(Compiler *compiler); + static CompilerValue *compileGt(Compiler *compiler); + static CompilerValue *compileAnd(Compiler *compiler); + static CompilerValue *compileOr(Compiler *compiler); + static CompilerValue *compileNot(Compiler *compiler); + static CompilerValue *compileJoin(Compiler *compiler); + static CompilerValue *compileLetterOf(Compiler *compiler); + static CompilerValue *compileLength(Compiler *compiler); + static CompilerValue *compileContains(Compiler *compiler); + static CompilerValue *compileMod(Compiler *compiler); + static CompilerValue *compileRound(Compiler *compiler); + static CompilerValue *compileMathOp(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/src/dev/blocks/sensingblocks.cpp b/src/dev/blocks/sensingblocks.cpp index 42896ecb..8e3b18ca 100644 --- a/src/dev/blocks/sensingblocks.cpp +++ b/src/dev/blocks/sensingblocks.cpp @@ -14,6 +14,11 @@ std::string SensingBlocks::description() const return name() + " blocks"; } +Rgb SensingBlocks::color() const +{ + return rgb(92, 177, 214); +} + void SensingBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/sensingblocks.h b/src/dev/blocks/sensingblocks.h index ab934fb0..9a0f367f 100644 --- a/src/dev/blocks/sensingblocks.h +++ b/src/dev/blocks/sensingblocks.h @@ -12,6 +12,7 @@ class SensingBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/soundblocks.cpp b/src/dev/blocks/soundblocks.cpp index 4a440a9a..df7611ab 100644 --- a/src/dev/blocks/soundblocks.cpp +++ b/src/dev/blocks/soundblocks.cpp @@ -14,6 +14,11 @@ std::string SoundBlocks::description() const return name() + " blocks"; } +Rgb SoundBlocks::color() const +{ + return rgb(207, 99, 207); +} + void SoundBlocks::registerBlocks(IEngine *engine) { } diff --git a/src/dev/blocks/soundblocks.h b/src/dev/blocks/soundblocks.h index afae6c9c..f605c7b6 100644 --- a/src/dev/blocks/soundblocks.h +++ b/src/dev/blocks/soundblocks.h @@ -12,6 +12,7 @@ class SoundBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; }; diff --git a/src/dev/blocks/variableblocks.cpp b/src/dev/blocks/variableblocks.cpp index 6d0f464f..9e77c7bf 100644 --- a/src/dev/blocks/variableblocks.cpp +++ b/src/dev/blocks/variableblocks.cpp @@ -1,5 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 +#include +#include +#include +#include +#include +#include + #include "variableblocks.h" using namespace libscratchcpp; @@ -14,6 +21,52 @@ std::string VariableBlocks::description() const return "Variable blocks"; } +Rgb VariableBlocks::color() const +{ + return rgb(255, 140, 26); +} + void VariableBlocks::registerBlocks(IEngine *engine) { + engine->addCompileFunction(this, "data_variable", &compileVariable); + engine->addCompileFunction(this, "data_setvariableto", &compileSetVariableTo); + engine->addCompileFunction(this, "data_changevariableby", &compileChangeVariableBy); +} + +CompilerValue *VariableBlocks::compileVariable(Compiler *compiler) +{ + Field *varField = compiler->field("VARIABLE"); + Variable *var = static_cast(varField->valuePtr().get()); + assert(var); + + if (var) + return compiler->addVariableValue(var); + else + return compiler->addConstValue(Value()); +} + +CompilerValue *VariableBlocks::compileSetVariableTo(Compiler *compiler) +{ + Field *varField = compiler->field("VARIABLE"); + Variable *var = static_cast(varField->valuePtr().get()); + assert(var); + + if (var) + compiler->createVariableWrite(var, compiler->addInput("VALUE")); + + return nullptr; +} + +CompilerValue *VariableBlocks::compileChangeVariableBy(Compiler *compiler) +{ + Field *varField = compiler->field("VARIABLE"); + Variable *var = static_cast(varField->valuePtr().get()); + assert(var); + + if (var) { + CompilerValue *value = compiler->createAdd(compiler->addVariableValue(var), compiler->addInput("VALUE")); + compiler->createVariableWrite(var, value); + } + + return nullptr; } diff --git a/src/dev/blocks/variableblocks.h b/src/dev/blocks/variableblocks.h index 6c4144ac..423085d3 100644 --- a/src/dev/blocks/variableblocks.h +++ b/src/dev/blocks/variableblocks.h @@ -12,8 +12,14 @@ class VariableBlocks : public IExtension public: std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; + + private: + static CompilerValue *compileVariable(Compiler *compiler); + static CompilerValue *compileSetVariableTo(Compiler *compiler); + static CompilerValue *compileChangeVariableBy(Compiler *compiler); }; } // namespace libscratchcpp diff --git a/src/dev/engine/CMakeLists.txt b/src/dev/engine/CMakeLists.txt index 0f4018fd..be71b580 100644 --- a/src/dev/engine/CMakeLists.txt +++ b/src/dev/engine/CMakeLists.txt @@ -3,17 +3,28 @@ target_sources(scratchcpp compiler.cpp compiler_p.cpp compiler_p.h + compilercontext.cpp + compilercontext_p.cpp + compilercontext_p.h + compilervalue.cpp + compilervalue_p.cpp + compilervalue_p.h + compilerconstant.cpp + compilerconstant_p.cpp + compilerconstant_p.h + compilerlocalvariable.cpp + compilerlocalvariable_p.cpp + compilerlocalvariable_p.h executioncontext.cpp executioncontext_p.cpp executioncontext_p.h + promise.cpp + promise_p.cpp + promise_p.h internal/icodebuilder.h internal/icodebuilderfactory.h internal/codebuilderfactory.cpp internal/codebuilderfactory.h - internal/llvmcodebuilder.cpp - internal/llvmcodebuilder.h - internal/llvmexecutablecode.cpp - internal/llvmexecutablecode.h - internal/llvmexecutioncontext.cpp - internal/llvmexecutioncontext.h ) + +add_subdirectory(internal/llvm) diff --git a/src/dev/engine/compiler.cpp b/src/dev/engine/compiler.cpp index 7ff6737e..23fc0045 100644 --- a/src/dev/engine/compiler.cpp +++ b/src/dev/engine/compiler.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include +#include #include #include #include @@ -11,7 +13,13 @@ using namespace libscratchcpp; -/*! Constructs Compiler. */ +/*! Constructs Compiler using the given context. */ +Compiler::Compiler(CompilerContext *ctx) : + impl(spimpl::make_unique_impl(ctx)) +{ +} + +/*! Constructs Compiler using a new context for the given target. */ Compiler::Compiler(IEngine *engine, Target *target) : impl(spimpl::make_unique_impl(engine, target)) { @@ -20,13 +28,13 @@ Compiler::Compiler(IEngine *engine, Target *target) : /*! Returns the Engine of the project. */ IEngine *Compiler::engine() const { - return impl->engine; + return impl->ctx->engine(); } /*! Returns the Target of this compiler. */ Target *Compiler::target() const { - return impl->target; + return impl->ctx->target(); } /*! Returns currently compiled block. */ @@ -38,16 +46,47 @@ std::shared_ptr Compiler::block() const /*! Compiles the script starting with the given block. */ std::shared_ptr Compiler::compile(std::shared_ptr startBlock) { - impl->builder = impl->builderFactory->create(startBlock->id(), false); + BlockPrototype *procedurePrototype = nullptr; + + if (startBlock) { + // TODO: Move procedure definition logic to the custom blocks extension + auto input = startBlock->inputAt(0); + + if (input && input->valueBlock()) { + procedurePrototype = input->valueBlock()->mutationPrototype(); + + if (procedurePrototype && procedurePrototype->procCode().empty()) + procedurePrototype = nullptr; + } + } + + impl->builder = impl->builderFactory->create(impl->ctx, procedurePrototype); impl->substackTree.clear(); impl->substackHit = false; + impl->emptySubstack = false; impl->warp = false; impl->block = startBlock; while (impl->block) { - if (impl->block->compileFunction()) + if (impl->block->compileFunction()) { + assert(impl->customIfStatementCount == 0); impl->block->compile(this); - else { + + if (impl->customIfStatementCount > 0) { + std::cerr << "error: if statement created by block '" << impl->block->opcode() << "' not terminated" << std::endl; + assert(false); + } + + if (impl->emptySubstack) { + impl->emptySubstack = false; + impl->substackEnd(); + } + + if (impl->customLoopCount > 0) { + std::cerr << "error: loop created by block '" << impl->block->opcode() << "' not terminated" << std::endl; + assert(false); + } + } else { std::cout << "warning: unsupported block: " << impl->block->opcode() << std::endl; impl->unsupportedBlocks.insert(impl->block->opcode()); } @@ -67,101 +106,471 @@ std::shared_ptr Compiler::compile(std::shared_ptr startBl return impl->builder->finalize(); } +/*! + * Optimizes all compiled scripts before they're called for the first time. + * \note Call this only after compiling all scripts. + */ +void Compiler::preoptimize() +{ + impl->ctx->preoptimize(); +} + /*! * Adds a call to the given function.\n + * For example: extern "C" bool some_block(double arg1, const char *arg2) + */ +CompilerValue *Compiler::addFunctionCall(const std::string &functionName, StaticType returnType, const ArgTypes &argTypes, const Args &args) +{ + assert(argTypes.size() == args.size()); + return impl->builder->addFunctionCall(functionName, returnType, argTypes, args); +} + +/*! + * Adds a call to the given function with a target parameter.\n * For example: extern "C" bool some_block(Target *target, double arg1, const char *arg2) */ -void Compiler::addFunctionCall(const std::string &functionName, StaticType returnType, const std::vector &argTypes) +CompilerValue *Compiler::addTargetFunctionCall(const std::string &functionName, StaticType returnType, const ArgTypes &argTypes, const Args &args) { - impl->builder->addFunctionCall(functionName, returnType, argTypes); + assert(argTypes.size() == args.size()); + return impl->builder->addTargetFunctionCall(functionName, returnType, argTypes, args); } -/*! Adds a constant value to the compiled code. */ -void Compiler::addConstValue(const Value &value) +/*! + * Adds a call to the given function with an execution context parameter.\n + * For example: extern "C" bool some_block(ExecutionContext *ctx, double arg1, const char *arg2) + */ +CompilerValue *Compiler::addFunctionCallWithCtx(const std::string &functionName, StaticType returnType, const ArgTypes &argTypes, const Args &args) +{ + assert(argTypes.size() == args.size()); + return impl->builder->addFunctionCallWithCtx(functionName, returnType, argTypes, args); +} + +/*! Adds the given constant to the compiled code. */ +CompilerConstant *Compiler::addConstValue(const Value &value) { - impl->builder->addConstValue(value); + return static_cast(impl->builder->addConstValue(value)); } -/*! Adds the given variable to the code (to read it). */ -void Compiler::addVariableValue(Variable *variable) +/*! Adds the index of the current repeat loop to the compiled code. */ +CompilerValue *Compiler::addLoopIndex() { - impl->builder->addVariableValue(variable); + return impl->builder->addLoopIndex(); } -/*! Adds the given list to the code (to read its string representation). */ -void Compiler::addListContents(List *list) +/*! Adds the value of the given local variable to the code. */ +CompilerValue *Compiler::addLocalVariableValue(CompilerLocalVariable *variable) { - impl->builder->addListContents(list); + return impl->builder->addLocalVariableValue(variable); +} + +/*! Adds the value of the given variable to the code. */ +CompilerValue *Compiler::addVariableValue(Variable *variable) +{ + return impl->builder->addVariableValue(variable); +} + +/*! Adds the string representation of the given list to the code. */ +CompilerValue *Compiler::addListContents(List *list) +{ + return impl->builder->addListContents(list); +} + +/*! Adds the item with index of the given list to the code. */ +CompilerValue *Compiler::addListItem(List *list, CompilerValue *index) +{ + return impl->builder->addListItem(list, index); +} + +/*! Adds the index of item of the given list to the code. */ +CompilerValue *Compiler::addListItemIndex(List *list, CompilerValue *item) +{ + return impl->builder->addListItemIndex(list, item); +} + +/*! Adds the result of a list contains item check to the code. */ +CompilerValue *Compiler::addListContains(List *list, CompilerValue *item) +{ + return impl->builder->addListContains(list, item); +} + +/*! Adds the length of the given list to the code. */ +CompilerValue *Compiler::addListSize(List *list) +{ + return impl->builder->addListSize(list); +} + +/*! Adds the procedure argument with the given name to the code. */ +CompilerValue *Compiler::addProcedureArgument(const std::string &name) +{ + return impl->builder->addProcedureArgument(name); } /*! Compiles the given input (resolved by name) and adds it to the compiled code. */ -void Compiler::addInput(const std::string &name) +CompilerValue *Compiler::addInput(const std::string &name) +{ + return addInput(impl->block->inputAt(impl->block->findInput(name)).get()); +} + +/*! Compiles the given input and adds it to the compiled code. */ +CompilerValue *Compiler::addInput(Input *input) +{ + if (!input) + return addConstValue(Value()); + + switch (input->type()) { + case Input::Type::Shadow: + case Input::Type::NoShadow: { + if (input->pointsToDropdownMenu()) + return addConstValue(input->selectedMenuItem()); + else { + CompilerValue *ret = nullptr; + auto previousBlock = impl->block; + impl->block = input->valueBlock(); + + if (impl->block) { + if (impl->block->compileFunction()) + ret = impl->block->compile(this); + else { + std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + ret = addConstValue(Value()); + } + } else + ret = addConstValue(input->primaryValue()->value()); + + impl->block = previousBlock; + return ret; + } + } + + case Input::Type::ObscuredShadow: { + CompilerValue *ret = nullptr; + auto previousBlock = impl->block; + impl->block = input->valueBlock(); + + if (impl->block) { + if (impl->block->compileFunction()) + ret = impl->block->compile(this); + else { + std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; + impl->unsupportedBlocks.insert(impl->block->opcode()); + ret = addConstValue(Value()); + } + } else + ret = input->primaryValue()->compile(this); + + impl->block = previousBlock; + return ret; + } + } + + return nullptr; +} + +/*! Creates an add instruction. */ +CompilerValue *Compiler::createAdd(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createAdd(operand1, operand2); +} + +/*! Creates a subtract instruction. */ +CompilerValue *Compiler::createSub(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createSub(operand1, operand2); +} + +/*! Creates a multiply instruction. */ +CompilerValue *Compiler::createMul(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createMul(operand1, operand2); +} + +/*! Creates a divide instruction. */ +CompilerValue *Compiler::createDiv(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createDiv(operand1, operand2); +} + +/*! Creates a random instruction (Scratch behavior). */ +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) +{ + return impl->builder->createCmpEQ(operand1, operand2); +} + +/*! Creates a greater than comparison instruction. */ +CompilerValue *Compiler::createCmpGT(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createCmpGT(operand1, operand2); +} + +/*! Creates a lower than comparison instruction. */ +CompilerValue *Compiler::createCmpLT(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createCmpLT(operand1, operand2); +} + +/*! Creates a string equality comparison (explicitly casts operands to string). */ +CompilerValue *Compiler::createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive) +{ + return impl->builder->createStrCmpEQ(string1, string2, caseSensitive); +} + +/*! Creates an AND operation. */ +CompilerValue *Compiler::createAnd(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createAnd(operand1, operand2); +} + +/*! Creates an OR operation. */ +CompilerValue *Compiler::createOr(CompilerValue *operand1, CompilerValue *operand2) +{ + return impl->builder->createOr(operand1, operand2); +} + +/*! Creates a NOT operation. */ +CompilerValue *Compiler::createNot(CompilerValue *operand) +{ + return impl->builder->createNot(operand); +} + +/*! Creates a remainder operation. */ +CompilerValue *Compiler::createMod(CompilerValue *num1, CompilerValue *num2) +{ + return impl->builder->createMod(num1, num2); +} + +/*! Creates a round operation. */ +CompilerValue *Compiler::createRound(CompilerValue *num) +{ + return impl->builder->createRound(num); +} + +/*! Creates an abs operation. */ +CompilerValue *Compiler::createAbs(CompilerValue *num) +{ + return impl->builder->createAbs(num); +} + +/*! Creates a floor operation. */ +CompilerValue *Compiler::createFloor(CompilerValue *num) +{ + return impl->builder->createFloor(num); +} + +/*! Creates a ceiling operation. */ +CompilerValue *Compiler::createCeil(CompilerValue *num) +{ + return impl->builder->createCeil(num); +} + +/*! Creates a square root operation. */ +CompilerValue *Compiler::createSqrt(CompilerValue *num) +{ + return impl->builder->createSqrt(num); +} + +/*! Creates a sin operation. */ +CompilerValue *Compiler::createSin(CompilerValue *num) +{ + return impl->builder->createSin(num); +} + +/*! Creates a cos operation. */ +CompilerValue *Compiler::createCos(CompilerValue *num) +{ + return impl->builder->createCos(num); +} + +/*! Creates a tan operation. */ +CompilerValue *Compiler::createTan(CompilerValue *num) +{ + return impl->builder->createTan(num); +} + +/*! Creates an asin operation. */ +CompilerValue *Compiler::createAsin(CompilerValue *num) +{ + return impl->builder->createAsin(num); +} + +/*! Creates an acos operation. */ +CompilerValue *Compiler::createAcos(CompilerValue *num) +{ + return impl->builder->createAcos(num); +} + +/*! Creates an atan operation. */ +CompilerValue *Compiler::createAtan(CompilerValue *num) { - addInput(impl->block->inputAt(impl->block->findInput(name)).get()); + return impl->builder->createAtan(num); } -/*! Creates an add instruction from the last 2 values. */ -void Compiler::createAdd() +/*! Creates an ln operation. */ +CompilerValue *Compiler::createLn(CompilerValue *num) { - impl->builder->createAdd(); + return impl->builder->createLn(num); } -/*! Creates a subtract instruction from the last 2 values. */ -void Compiler::createSub() +/*! Creates a log10 operation. */ +CompilerValue *Compiler::createLog10(CompilerValue *num) { - impl->builder->createSub(); + return impl->builder->createLog10(num); } -/*! Creates a multiply instruction from the last 2 values. */ -void Compiler::createMul() +/*! Creates an e^x operation. */ +CompilerValue *Compiler::createExp(CompilerValue *num) { - impl->builder->createMul(); + return impl->builder->createExp(num); } -/*! Creates a divide instruction from the last 2 values. */ -void Compiler::createDiv() +/*! Creates a 10^x operation. */ +CompilerValue *Compiler::createExp10(CompilerValue *num) +{ + return impl->builder->createExp10(num); +} + +/*! Creates a select instruction (ternary operator). */ +CompilerValue *Compiler::createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, StaticType valueType) +{ + 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) +{ + impl->builder->createVariableWrite(variable, value); +} + +/*! Creates a clear list operation. */ +void Compiler::createListClear(List *list) +{ + impl->builder->createListClear(list); +} + +/*! + * Creates a remove item from list operation. + * \note The index starts with 0 and is cast to number, special strings like "last" are not handled. + */ +void Compiler::createListRemove(List *list, CompilerValue *index) +{ + impl->builder->createListRemove(list, index); +} + +/*! Creates a list append operation. */ +void Compiler::createListAppend(List *list, CompilerValue *item) +{ + impl->builder->createListAppend(list, item); +} + +/*! Creates a list insert operation. */ +void Compiler::createListInsert(List *list, CompilerValue *index, CompilerValue *item) +{ + impl->builder->createListInsert(list, index, item); +} + +/*! Creates a list replace operation. */ +void Compiler::createListReplace(List *list, CompilerValue *index, CompilerValue *item) +{ + impl->builder->createListReplace(list, index, item); +} + +/*! + * Starts a custom if statement. + * \note The if statement must be terminated using endIf() after compiling your block. + */ +void Compiler::beginIfStatement(CompilerValue *cond) { - impl->builder->createDiv(); + impl->builder->beginIfStatement(cond); + impl->customIfStatementCount++; } -/*! Creates an equality comparison instruction using the last 2 values. */ -void Compiler::createCmpEQ() +/*! Starts the else branch of custom if statement. */ +void Compiler::beginElseBranch() { - impl->builder->createCmpEQ(); + impl->builder->beginElseBranch(); } -/*! Creates a greater than comparison instruction using the last 2 values. */ -void Compiler::createCmpGT() +/*! Ends custom if statement. */ +void Compiler::endIf() { - impl->builder->createCmpGT(); + if (impl->customIfStatementCount == 0) { + std::cerr << "error: called Compiler::endIf() without an if statement"; + assert(false); + return; + } + + impl->builder->endIf(); + impl->customIfStatementCount--; } -/*! Creates a lower than comparison instruction using the last 2 values. */ -void Compiler::createCmpLT() +/*! + * Begins a custom while loop. + * \note The loop must be terminated with endLoop() after compiling your block. + */ +void Compiler::beginWhileLoop(CompilerValue *cond) { - impl->builder->createCmpLT(); + impl->builder->beginWhileLoop(cond); + impl->customLoopCount++; } -/*! Creates an AND operation using the last 2 values. */ -void Compiler::createAnd() +/*! + * Begins a custom repeat until loop. + * \note The loop must be terminated with endLoop() after compiling your block. + */ +void Compiler::beginRepeatUntilLoop(CompilerValue *cond) { - impl->builder->createAnd(); + impl->builder->beginRepeatUntilLoop(cond); + impl->customLoopCount++; } -/*! Creates an OR operation using the last 2 values. */ -void Compiler::createOr() +/*! Begins a while/until loop condition. */ +void Compiler::beginLoopCondition() { - impl->builder->createOr(); + impl->builder->beginLoopCondition(); } -/*! Creates a NOT operation using the last value. */ -void Compiler::createNot() +/*! Ends custom loop. */ +void Compiler::endLoop() { - impl->builder->createNot(); + if (impl->customLoopCount == 0) { + std::cerr << "error: called Compiler::endLoop() without a loop"; + assert(false); + return; + } + + impl->builder->endLoop(); + impl->customLoopCount--; } /*! Jumps to the given if substack. */ -void Compiler::moveToIf(std::shared_ptr substack) +void Compiler::moveToIf(CompilerValue *cond, std::shared_ptr substack) { if (!substack) return; // ignore empty if statements @@ -169,11 +578,11 @@ void Compiler::moveToIf(std::shared_ptr substack) impl->substackHit = true; impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::IfStatement }); impl->block = substack; - impl->builder->beginIfStatement(); + impl->builder->beginIfStatement(cond); } /*! 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) +void Compiler::moveToIfElse(CompilerValue *cond, std::shared_ptr substack1, std::shared_ptr substack2) { if (!substack1 && !substack2) return; // ignore empty if statements @@ -181,52 +590,46 @@ void Compiler::moveToIfElse(std::shared_ptr substack1, std::shared_ptrsubstackHit = true; impl->substackTree.push_back({ { impl->block, substack2 }, CompilerPrivate::SubstackType::IfStatement }); impl->block = substack1; - impl->builder->beginIfStatement(); + impl->builder->beginIfStatement(cond); if (!impl->block) - impl->substackEnd(); + impl->emptySubstack = true; } /*! Jumps to the given repeat loop substack. */ -void Compiler::moveToRepeatLoop(std::shared_ptr substack) +void Compiler::moveToRepeatLoop(CompilerValue *count, std::shared_ptr substack) { impl->substackHit = true; impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); impl->block = substack; - impl->builder->beginRepeatLoop(); + impl->builder->beginRepeatLoop(count); if (!impl->block) - impl->substackEnd(); + impl->emptySubstack = true; } /*! Jumps to the given while loop substack. */ -void Compiler::moveToWhileLoop(std::shared_ptr substack) +void Compiler::moveToWhileLoop(CompilerValue *cond, std::shared_ptr substack) { impl->substackHit = true; impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); impl->block = substack; - impl->builder->beginWhileLoop(); + impl->builder->beginWhileLoop(cond); if (!impl->block) - impl->substackEnd(); + impl->emptySubstack = true; } /*! Jumps to the given until loop substack. */ -void Compiler::moveToRepeatUntilLoop(std::shared_ptr substack) +void Compiler::moveToRepeatUntilLoop(CompilerValue *cond, std::shared_ptr substack) { impl->substackHit = true; impl->substackTree.push_back({ { impl->block, nullptr }, CompilerPrivate::SubstackType::Loop }); impl->block = substack; - impl->builder->beginRepeatUntilLoop(); + impl->builder->beginRepeatUntilLoop(cond); if (!impl->block) - impl->substackEnd(); -} - -/*! Begins a while/until loop condition. */ -void Compiler::beginLoopCondition() -{ - impl->builder->beginLoopCondition(); + impl->emptySubstack = true; } /*! Makes current script run without screen refresh. */ @@ -235,6 +638,24 @@ void Compiler::warp() impl->warp = true; } +/*! Creates a suspend instruction. */ +void Compiler::createYield() +{ + impl->builder->yield(); +} + +/*! Creates a stop script instruction. */ +void Compiler::createStop() +{ + impl->builder->createStop(); +} + +/*! Creates a call to the procedure with the given prototype. */ +void Compiler::createProcedureCall(BlockPrototype *prototype, const libscratchcpp::Compiler::Args &args) +{ + impl->builder->createProcedureCall(prototype, args); +} + /*! Convenience method which returns the field with the given name. */ Input *Compiler::input(const std::string &name) const { @@ -253,55 +674,9 @@ const std::unordered_set &Compiler::unsupportedBlocks() const return impl->unsupportedBlocks; } -void Compiler::addInput(Input *input) +/*! Creates a compiler context for the given target. */ +std::shared_ptr Compiler::createContext(IEngine *engine, Target *target) { - if (!input) { - addConstValue(Value()); - return; - } - - switch (input->type()) { - case Input::Type::Shadow: - case Input::Type::NoShadow: { - if (input->pointsToDropdownMenu()) - addConstValue(input->selectedMenuItem()); - else { - auto previousBlock = impl->block; - impl->block = input->valueBlock(); - - if (impl->block) { - if (impl->block->compileFunction()) - impl->block->compile(this); - else { - std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; - impl->unsupportedBlocks.insert(impl->block->opcode()); - addConstValue(Value()); - } - } else - addConstValue(input->primaryValue()->value()); - - impl->block = previousBlock; - } - - break; - } - - case Input::Type::ObscuredShadow: { - auto previousBlock = impl->block; - impl->block = input->valueBlock(); - if (impl->block) { - if (impl->block->compileFunction()) - impl->block->compile(this); - else { - std::cout << "warning: unsupported reporter block: " << impl->block->opcode() << std::endl; - impl->unsupportedBlocks.insert(impl->block->opcode()); - addConstValue(Value()); - } - } else - input->primaryValue()->compile(this); - - impl->block = previousBlock; - break; - } - } + CompilerPrivate::initBuilderFactory(); + return CompilerPrivate::builderFactory->createCtx(engine, target); } diff --git a/src/dev/engine/compiler_p.cpp b/src/dev/engine/compiler_p.cpp index cff14fe9..963c0d3e 100644 --- a/src/dev/engine/compiler_p.cpp +++ b/src/dev/engine/compiler_p.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 +#include #include #include "compiler_p.h" @@ -9,9 +10,21 @@ using namespace libscratchcpp; +CompilerPrivate::CompilerPrivate(CompilerContext *ctx) : + ctx(ctx) +{ + assert(ctx); + initBuilderFactory(); +} + CompilerPrivate::CompilerPrivate(IEngine *engine, Target *target) : - engine(engine), - target(target) + ctxPtr(Compiler::createContext(engine, target)), + ctx(ctxPtr.get()) +{ + initBuilderFactory(); +} + +void CompilerPrivate::initBuilderFactory() { if (!builderFactory) builderFactory = CodeBuilderFactory::instance().get(); diff --git a/src/dev/engine/compiler_p.h b/src/dev/engine/compiler_p.h index 059d143b..593accbf 100644 --- a/src/dev/engine/compiler_p.h +++ b/src/dev/engine/compiler_p.h @@ -9,9 +9,11 @@ namespace libscratchcpp { +class CompilerContext; class IEngine; class Target; class Block; +class CompilerValue; class ICodeBuilderFactory; class ICodeBuilder; @@ -23,16 +25,22 @@ struct CompilerPrivate IfStatement }; + CompilerPrivate(CompilerContext *ctx); CompilerPrivate(IEngine *engine, Target *target); + static void initBuilderFactory(); + void substackEnd(); - IEngine *engine = nullptr; - Target *target = nullptr; + std::shared_ptr ctxPtr; // for self-managed contexts + CompilerContext *ctx = nullptr; std::shared_ptr block; + int customIfStatementCount = 0; + int customLoopCount = 0; std::vector, std::shared_ptr>, SubstackType>> substackTree; bool substackHit = false; + bool emptySubstack = false; bool warp = false; static inline ICodeBuilderFactory *builderFactory = nullptr; diff --git a/src/dev/engine/compilerconstant.cpp b/src/dev/engine/compilerconstant.cpp new file mode 100644 index 00000000..fb7dcf3b --- /dev/null +++ b/src/dev/engine/compilerconstant.cpp @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "compilerconstant_p.h" + +using namespace libscratchcpp; + +/*! Constructs CompilerConstant. */ +CompilerConstant::CompilerConstant(Compiler::StaticType type, const Value &value) : + CompilerValue(type), + impl(spimpl::make_unique_impl(value)) +{ +} + +/*! Returns the constant value. */ +const Value &CompilerConstant::value() const +{ + return impl->value; +} diff --git a/src/dev/engine/compilerconstant_p.cpp b/src/dev/engine/compilerconstant_p.cpp new file mode 100644 index 00000000..c1e0b07e --- /dev/null +++ b/src/dev/engine/compilerconstant_p.cpp @@ -0,0 +1,8 @@ +#include "compilerconstant_p.h" + +using namespace libscratchcpp; + +CompilerConstantPrivate::CompilerConstantPrivate(const Value &value) : + value(value) +{ +} diff --git a/src/dev/engine/compilerconstant_p.h b/src/dev/engine/compilerconstant_p.h new file mode 100644 index 00000000..c7b983ef --- /dev/null +++ b/src/dev/engine/compilerconstant_p.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +struct CompilerConstantPrivate +{ + CompilerConstantPrivate(const Value &value); + + Value value; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/compilercontext.cpp b/src/dev/engine/compilercontext.cpp new file mode 100644 index 00000000..12bc613e --- /dev/null +++ b/src/dev/engine/compilercontext.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "compilercontext_p.h" + +using namespace libscratchcpp; + +/*! Constructs CompilerContext. */ +CompilerContext::CompilerContext(IEngine *engine, Target *target) : + impl(spimpl::make_unique_impl(engine, target)) +{ +} + +/*! Returns the engine of the project. */ +IEngine *CompilerContext::engine() const +{ + return impl->engine; +} + +/*! Returns the target of this context. */ +Target *CompilerContext::target() const +{ + return impl->target; +} diff --git a/src/dev/engine/compilercontext_p.cpp b/src/dev/engine/compilercontext_p.cpp new file mode 100644 index 00000000..5f4c42f2 --- /dev/null +++ b/src/dev/engine/compilercontext_p.cpp @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "compilercontext_p.h" + +using namespace libscratchcpp; + +CompilerContextPrivate::CompilerContextPrivate(IEngine *engine, Target *target) : + engine(engine), + target(target) +{ +} diff --git a/src/dev/engine/compilercontext_p.h b/src/dev/engine/compilercontext_p.h new file mode 100644 index 00000000..5a97a56a --- /dev/null +++ b/src/dev/engine/compilercontext_p.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace libscratchcpp +{ + +class IEngine; +class Target; + +struct CompilerContextPrivate +{ + CompilerContextPrivate(IEngine *engine, Target *target); + + IEngine *engine = nullptr; + Target *target = nullptr; +}; + +} // namespace libscratchcpp 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/compilervalue.cpp b/src/dev/engine/compilervalue.cpp new file mode 100644 index 00000000..9a601321 --- /dev/null +++ b/src/dev/engine/compilervalue.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "compilervalue_p.h" + +using namespace libscratchcpp; + +/*! Constructs CompilerValue. */ +CompilerValue::CompilerValue(Compiler::StaticType type) : + impl(spimpl::make_unique_impl(type)) +{ +} + +/*! Returns the type of this value. */ +Compiler::StaticType CompilerValue::type() const +{ + return impl->type; +} + +/*! Sets the type of this value. */ +void CompilerValue::setType(Compiler::StaticType type) +{ + impl->type = type; +} diff --git a/src/dev/engine/compilervalue_p.cpp b/src/dev/engine/compilervalue_p.cpp new file mode 100644 index 00000000..a526ae6e --- /dev/null +++ b/src/dev/engine/compilervalue_p.cpp @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "compilervalue_p.h" + +using namespace libscratchcpp; + +CompilerValuePrivate::CompilerValuePrivate(Compiler::StaticType type) : + type(type) +{ +} diff --git a/src/dev/engine/compilervalue_p.h b/src/dev/engine/compilervalue_p.h new file mode 100644 index 00000000..99a9112e --- /dev/null +++ b/src/dev/engine/compilervalue_p.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +struct CompilerValuePrivate +{ + CompilerValuePrivate(Compiler::StaticType type); + + Compiler::StaticType type = Compiler::StaticType::Unknown; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/executioncontext.cpp b/src/dev/engine/executioncontext.cpp index c45156c6..fb0d9327 100644 --- a/src/dev/engine/executioncontext.cpp +++ b/src/dev/engine/executioncontext.cpp @@ -1,19 +1,62 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include "executioncontext_p.h" using namespace libscratchcpp; /*! Constructs ExecutionContext. */ -ExecutionContext::ExecutionContext(Target *target) : - impl(spimpl::make_unique_impl(target)) +ExecutionContext::ExecutionContext(Thread *thread) : + impl(spimpl::make_unique_impl(thread)) { } -/*! Returns the Target of this context. */ -Target *ExecutionContext::target() const +/*! Returns the thread of this context. */ +Thread *ExecutionContext::thread() const { - return impl->target; + return impl->thread; +} + +/*! Returns the engine of the project. */ +IEngine *ExecutionContext::engine() const +{ + return impl->thread->engine(); +} + +/*! Returns the script promise. */ +std::shared_ptr ExecutionContext::promise() const +{ + return impl->promise; +} + +/*! Sets the script promise (yields until the promise is resolved). */ +void ExecutionContext::setPromise(std::shared_ptr promise) +{ + impl->promise = promise; +} + +/*! Returns the stack timer of this context. Can be used for wait blocks. */ +IStackTimer *ExecutionContext::stackTimer() const +{ + return impl->stackTimer; +} + +/*! Sets a custom stack timer. */ +void ExecutionContext::setStackTimer(IStackTimer *newStackTimer) +{ + impl->stackTimer = newStackTimer; +} + +/*! Returns the random number generator of this context. */ +IRandomGenerator *ExecutionContext::rng() const +{ + return impl->rng; +} + +/*! Sets a custom random number generator. */ +void ExecutionContext::setRng(IRandomGenerator *newRng) +{ + impl->rng = newRng; } diff --git a/src/dev/engine/executioncontext_p.cpp b/src/dev/engine/executioncontext_p.cpp index f2efa1e1..94dd1072 100644 --- a/src/dev/engine/executioncontext_p.cpp +++ b/src/dev/engine/executioncontext_p.cpp @@ -1,10 +1,14 @@ // SPDX-License-Identifier: Apache-2.0 #include "executioncontext_p.h" +#include "../../engine/internal/randomgenerator.h" using namespace libscratchcpp; -ExecutionContextPrivate::ExecutionContextPrivate(Target *target) : - target(target) +ExecutionContextPrivate::ExecutionContextPrivate(Thread *thread) : + thread(thread), + defaultStackTimer(std::make_unique()), + stackTimer(defaultStackTimer.get()), + rng(RandomGenerator::instance().get()) { } diff --git a/src/dev/engine/executioncontext_p.h b/src/dev/engine/executioncontext_p.h index fd7fad51..367452aa 100644 --- a/src/dev/engine/executioncontext_p.h +++ b/src/dev/engine/executioncontext_p.h @@ -2,16 +2,26 @@ #pragma once +#include + +#include "../../engine/internal/stacktimer.h" + namespace libscratchcpp { -class Target; +class Thread; +class Promise; +class IRandomGenerator; struct ExecutionContextPrivate { - ExecutionContextPrivate(Target *target); + ExecutionContextPrivate(Thread *thread); - Target *target = nullptr; + Thread *thread = nullptr; + std::shared_ptr promise; + std::unique_ptr defaultStackTimer; + IStackTimer *stackTimer = nullptr; + IRandomGenerator *rng = nullptr; }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/codebuilderfactory.cpp b/src/dev/engine/internal/codebuilderfactory.cpp index 5f955bdc..bb3cb1e9 100644 --- a/src/dev/engine/internal/codebuilderfactory.cpp +++ b/src/dev/engine/internal/codebuilderfactory.cpp @@ -1,7 +1,8 @@ // SPDX-License-Identifier: Apache-2.0 #include "codebuilderfactory.h" -#include "llvmcodebuilder.h" +#include "llvm/llvmcodebuilder.h" +#include "llvm/llvmcompilercontext.h" using namespace libscratchcpp; @@ -12,7 +13,14 @@ std::shared_ptr CodeBuilderFactory::instance() return m_instance; } -std::shared_ptr CodeBuilderFactory::create(const std::string &id, bool warp) const +std::shared_ptr CodeBuilderFactory::create(CompilerContext *ctx, BlockPrototype *procedurePrototype) const { - return std::make_shared(id, warp); + assert(dynamic_cast(ctx)); + return std::make_shared(static_cast(ctx), procedurePrototype); +} + +std::shared_ptr CodeBuilderFactory::createCtx(IEngine *engine, Target *target) const +{ + auto ptr = std::make_shared(engine, target); + return ptr; } diff --git a/src/dev/engine/internal/codebuilderfactory.h b/src/dev/engine/internal/codebuilderfactory.h index 8de574ab..942a9a1e 100644 --- a/src/dev/engine/internal/codebuilderfactory.h +++ b/src/dev/engine/internal/codebuilderfactory.h @@ -11,7 +11,8 @@ class CodeBuilderFactory : public ICodeBuilderFactory { public: static std::shared_ptr instance(); - std::shared_ptr create(const std::string &id, bool warp) const override; + std::shared_ptr create(CompilerContext *ctx, BlockPrototype *procedurePrototype) const override; + std::shared_ptr createCtx(IEngine *engine, Target *target) const override; private: static std::shared_ptr m_instance; diff --git a/src/dev/engine/internal/icodebuilder.h b/src/dev/engine/internal/icodebuilder.h index f5852098..fb41eb89 100644 --- a/src/dev/engine/internal/icodebuilder.h +++ b/src/dev/engine/internal/icodebuilder.h @@ -11,6 +11,7 @@ class Value; class Variable; class List; class ExecutableCode; +class BlockPrototype; class ICodeBuilder { @@ -19,35 +20,83 @@ class ICodeBuilder virtual std::shared_ptr finalize() = 0; - virtual void addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector &argTypes) = 0; - virtual void addConstValue(const Value &value) = 0; - virtual void addVariableValue(Variable *variable) = 0; - virtual void addListContents(List *list) = 0; + virtual CompilerValue *addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0; + virtual CompilerValue *addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) = 0; + 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; + virtual CompilerValue *addListItemIndex(List *list, CompilerValue *item) = 0; + virtual CompilerValue *addListContains(List *list, CompilerValue *item) = 0; + virtual CompilerValue *addListSize(List *list) = 0; + virtual CompilerValue *addProcedureArgument(const std::string &name) = 0; - virtual void createAdd() = 0; - virtual void createSub() = 0; - virtual void createMul() = 0; - virtual void createDiv() = 0; + virtual CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createDiv(CompilerValue *operand1, CompilerValue *operand2) = 0; - virtual void createCmpEQ() = 0; - virtual void createCmpGT() = 0; - virtual void createCmpLT() = 0; + virtual CompilerValue *createRandom(CompilerValue *from, CompilerValue *to) = 0; + virtual CompilerValue *createRandomInt(CompilerValue *from, CompilerValue *to) = 0; - virtual void createAnd() = 0; - virtual void createOr() = 0; - virtual void createNot() = 0; + virtual CompilerValue *createCmpEQ(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createCmpGT(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) = 0; - virtual void beginIfStatement() = 0; + virtual CompilerValue *createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive = false) = 0; + + virtual CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2) = 0; + virtual CompilerValue *createNot(CompilerValue *operand) = 0; + + virtual CompilerValue *createMod(CompilerValue *num1, CompilerValue *num2) = 0; + virtual CompilerValue *createRound(CompilerValue *num) = 0; + virtual CompilerValue *createAbs(CompilerValue *num) = 0; + virtual CompilerValue *createFloor(CompilerValue *num) = 0; + virtual CompilerValue *createCeil(CompilerValue *num) = 0; + virtual CompilerValue *createSqrt(CompilerValue *num) = 0; + virtual CompilerValue *createSin(CompilerValue *num) = 0; + virtual CompilerValue *createCos(CompilerValue *num) = 0; + virtual CompilerValue *createTan(CompilerValue *num) = 0; + virtual CompilerValue *createAsin(CompilerValue *num) = 0; + virtual CompilerValue *createAcos(CompilerValue *num) = 0; + virtual CompilerValue *createAtan(CompilerValue *num) = 0; + virtual CompilerValue *createLn(CompilerValue *num) = 0; + virtual CompilerValue *createLog10(CompilerValue *num) = 0; + virtual CompilerValue *createExp(CompilerValue *num) = 0; + virtual CompilerValue *createExp10(CompilerValue *num) = 0; + + 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; + virtual void createListRemove(List *list, CompilerValue *index) = 0; + virtual void createListAppend(List *list, CompilerValue *item) = 0; + virtual void createListInsert(List *list, CompilerValue *index, CompilerValue *item) = 0; + virtual void createListReplace(List *list, CompilerValue *index, CompilerValue *item) = 0; + + virtual void beginIfStatement(CompilerValue *cond) = 0; virtual void beginElseBranch() = 0; virtual void endIf() = 0; - virtual void beginRepeatLoop() = 0; - virtual void beginWhileLoop() = 0; - virtual void beginRepeatUntilLoop() = 0; + virtual void beginRepeatLoop(CompilerValue *count) = 0; + virtual void beginWhileLoop(CompilerValue *cond) = 0; + virtual void beginRepeatUntilLoop(CompilerValue *cond) = 0; virtual void beginLoopCondition() = 0; virtual void endLoop() = 0; virtual void yield() = 0; + + virtual void createStop() = 0; + + virtual void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) = 0; }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/icodebuilderfactory.h b/src/dev/engine/internal/icodebuilderfactory.h index 4438e5f8..6e031b2d 100644 --- a/src/dev/engine/internal/icodebuilderfactory.h +++ b/src/dev/engine/internal/icodebuilderfactory.h @@ -8,13 +8,18 @@ namespace libscratchcpp { class ICodeBuilder; +class CompilerContext; +class BlockPrototype; +class Target; +class IEngine; class ICodeBuilderFactory { public: virtual ~ICodeBuilderFactory() { } - virtual std::shared_ptr create(const std::string &id, bool warp) const = 0; + virtual std::shared_ptr create(CompilerContext *ctx, BlockPrototype *procedurePrototype = nullptr) const = 0; + virtual std::shared_ptr createCtx(IEngine *engine, Target *target) const = 0; }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/CMakeLists.txt b/src/dev/engine/internal/llvm/CMakeLists.txt new file mode 100644 index 00000000..38b7e4d2 --- /dev/null +++ b/src/dev/engine/internal/llvm/CMakeLists.txt @@ -0,0 +1,26 @@ +target_sources(scratchcpp + PRIVATE + llvmcodebuilder.cpp + llvmcodebuilder.h + llvmregisterbase.h + llvmregister.h + llvmconstantregister.h + llvminstruction.h + llvmifstatement.h + llvmloop.h + llvmcoroutine.cpp + llvmcoroutine.h + llvmvariableptr.h + llvmlistptr.h + llvmprocedure.h + llvmtypes.cpp + llvmtypes.h + llvmfunctions.cpp + llvmloopscope.h + llvmcompilercontext.cpp + llvmcompilercontext.h + llvmexecutablecode.cpp + llvmexecutablecode.h + llvmexecutioncontext.cpp + llvmexecutioncontext.h +) diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.cpp b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp new file mode 100644 index 00000000..0c0a9743 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.cpp @@ -0,0 +1,3208 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include +#include +#include +#include +#include +#include + +#include "llvmcodebuilder.h" +#include "llvmcompilercontext.h" +#include "llvmexecutablecode.h" +#include "llvmconstantregister.h" +#include "llvmifstatement.h" +#include "llvmloop.h" +#include "llvmtypes.h" +#include "llvmloopscope.h" + +using namespace libscratchcpp; + +static std::unordered_map + TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } }; + +static const std::unordered_set + VAR_LIST_READ_INSTRUCTIONS = { LLVMInstruction::Type::ReadVariable, LLVMInstruction::Type::GetListItem, LLVMInstruction::Type::GetListItemIndex, LLVMInstruction::Type::ListContainsItem }; + +LLVMCodeBuilder::LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *procedurePrototype) : + m_ctx(ctx), + m_target(ctx->target()), + m_llvmCtx(*ctx->llvmCtx()), + m_module(ctx->module()), + m_builder(m_llvmCtx), + m_procedurePrototype(procedurePrototype), + m_defaultWarp(procedurePrototype ? procedurePrototype->warp() : false), + m_warp(m_defaultWarp) +{ + initTypes(); + createVariableMap(); + createListMap(); + + llvm::FunctionType *funcType = getMainFunctionType(nullptr); + m_defaultArgCount = funcType->getNumParams(); +} + +std::shared_ptr LLVMCodeBuilder::finalize() +{ + if (!m_warp) { + // Do not create coroutine if there are no yield instructions nor non-warp procedure calls + auto it = std::find_if(m_instructions.begin(), m_instructions.end(), [](const std::shared_ptr &step) { + return step->type == LLVMInstruction::Type::Yield || (step->type == LLVMInstruction::Type::CallProcedure && step->procedurePrototype && !step->procedurePrototype->warp()); + }); + + if (it == m_instructions.end()) + m_warp = true; + } + + // Set fast math flags + llvm::FastMathFlags fmf; + fmf.setFast(true); + fmf.setNoInfs(false); + fmf.setNoNaNs(false); + fmf.setNoSignedZeros(false); + m_builder.setFastMathFlags(fmf); + + // Create function + std::string funcName = getMainFunctionName(m_procedurePrototype); + llvm::FunctionType *funcType = getMainFunctionType(m_procedurePrototype); + + if (m_procedurePrototype) + m_function = getOrCreateFunction(funcName, funcType); + else + m_function = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, funcName, m_module); + + llvm::Value *executionContextPtr = m_function->getArg(0); + llvm::Value *targetPtr = m_function->getArg(1); + llvm::Value *targetVariables = m_function->getArg(2); + llvm::Value *targetLists = m_function->getArg(3); + llvm::Value *warpArg = nullptr; + + if (m_procedurePrototype) + warpArg = m_function->getArg(4); + + if (m_procedurePrototype && m_warp) + m_function->addFnAttr(llvm::Attribute::InlineHint); + + llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", m_function); + llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_llvmCtx, "end", m_function); + m_builder.SetInsertPoint(entry); + + // Init coroutine + std::unique_ptr coro; + + if (!m_warp) + coro = std::make_unique(m_module, &m_builder, m_function); + + std::vector ifStatements; + std::vector loops; + m_heap.clear(); + + // Create variable pointers + for (auto &[var, varPtr] : m_variablePtrs) { + llvm::Value *ptr = getVariablePtr(targetVariables, var); + + // Direct access + varPtr.heapPtr = ptr; + + // All variables are currently created on the stack and synced later (seems to be faster) + // NOTE: Strings are NOT copied, only the pointer and string size are copied + varPtr.stackPtr = m_builder.CreateAlloca(m_valueDataType); + + // If there are no write operations outside loops, initialize the stack variable now + Variable *variable = var; + auto it = std::find_if(m_variableInstructions.begin(), m_variableInstructions.end(), [variable](const std::shared_ptr &ins) { + return ins->type == LLVMInstruction::Type::WriteVariable && ins->workVariable == variable && !ins->loopScope; + }); + + if (it == m_variableInstructions.end()) { + createValueCopy(ptr, varPtr.stackPtr); + varPtr.onStack = true; + } else + varPtr.onStack = false; // use heap before the first assignment + } + + // Create list pointers + for (auto &[list, listPtr] : m_listPtrs) { + listPtr.ptr = getListPtr(targetLists, list); + + listPtr.dataPtr = m_builder.CreateAlloca(m_valueDataType->getPointerTo()); + m_builder.CreateStore(m_builder.CreateCall(resolve_list_data(), listPtr.ptr), listPtr.dataPtr); + + listPtr.sizePtr = m_builder.CreateCall(resolve_list_size_ptr(), listPtr.ptr); + listPtr.allocatedSizePtr = m_builder.CreateCall(resolve_list_alloc_size_ptr(), listPtr.ptr); + + listPtr.dataPtrDirty = m_builder.CreateAlloca(m_builder.getInt1Ty()); + m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); + } + + assert(m_loopScope == -1); + m_loopScope = -1; + m_scopeVariables.clear(); + m_scopeLists.clear(); + pushScopeLevel(); + + // Execute recorded steps + for (const auto insPtr : m_instructions) { + const LLVMInstruction &step = *insPtr; + + switch (step.type) { + case LLVMInstruction::Type::FunctionCall: { + std::vector types; + std::vector args; + + // Add execution context arg + if (step.functionCtxArg) { + types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0)); + args.push_back(executionContextPtr); + } + + // Add target pointer arg + if (step.functionTargetArg) { + types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0)); + args.push_back(targetPtr); + } + + // Args + for (auto &arg : step.args) { + types.push_back(getType(arg.first)); + args.push_back(castValue(arg.second, arg.first)); + } + + llvm::Type *retType = getType(step.functionReturnReg ? step.functionReturnReg->type() : Compiler::StaticType::Void); + llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(retType, types, false)), args); + + if (step.functionReturnReg) { + step.functionReturnReg->value = ret; + + if (step.functionReturnReg->type() == Compiler::StaticType::String) + freeLater(step.functionReturnReg->value); + } + + break; + } + + case LLVMInstruction::Type::Add: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFAdd(num1, num2); + break; + } + + case LLVMInstruction::Type::Sub: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFSub(num1, num2); + break; + } + + case LLVMInstruction::Type::Mul: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFMul(num1, num2); + break; + } + + case LLVMInstruction::Type::Div: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + step.functionReturnReg->value = m_builder.CreateFDiv(num1, num2); + break; + } + + case LLVMInstruction::Type::Random: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + LLVMRegister *reg1 = arg1.second; + LLVMRegister *reg2 = arg2.second; + + if (reg1->type() == Compiler::StaticType::Bool && reg2->type() == Compiler::StaticType::Bool) { + llvm::Value *bool1 = castValue(arg1.second, Compiler::StaticType::Bool); + llvm::Value *bool2 = castValue(arg2.second, Compiler::StaticType::Bool); + step.functionReturnReg->value = m_builder.CreateCall(resolve_llvm_random_bool(), { executionContextPtr, bool1, bool2 }); + } else { + llvm::Constant *inf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false); + llvm::Value *num1 = removeNaN(castValue(arg1.second, Compiler::StaticType::Number)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, Compiler::StaticType::Number)); + llvm::Value *sum = m_builder.CreateFAdd(num1, num2); + llvm::Value *sumDiv = m_builder.CreateFDiv(sum, inf); + llvm::Value *isInfOrNaN = isNaN(sumDiv); + + // NOTE: The random function will be called even in edge cases where it isn't needed, but they're rare, so it shouldn't be an issue + if (reg1->type() == Compiler::StaticType::Number && reg2->type() == Compiler::StaticType::Number) + step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random_double(), { executionContextPtr, num1, num2 })); + else { + llvm::Value *value1 = createValue(reg1); + llvm::Value *value2 = createValue(reg2); + step.functionReturnReg->value = m_builder.CreateSelect(isInfOrNaN, sum, m_builder.CreateCall(resolve_llvm_random(), { executionContextPtr, value1, value2 })); + } + } + + 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; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::EQ); + break; + } + + case LLVMInstruction::Type::CmpGT: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0].second; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::GT); + break; + } + + case LLVMInstruction::Type::CmpLT: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0].second; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::LT); + break; + } + + case LLVMInstruction::Type::StrCmpEQCS: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0].second; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createStringComparison(arg1, arg2, true); + break; + } + + case LLVMInstruction::Type::StrCmpEQCI: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0].second; + const auto &arg2 = step.args[1].second; + step.functionReturnReg->value = createStringComparison(arg1, arg2, false); + break; + } + + case LLVMInstruction::Type::And: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *bool1 = castValue(arg1.second, arg1.first); + llvm::Value *bool2 = castValue(arg2.second, arg2.first); + step.functionReturnReg->value = m_builder.CreateAnd(bool1, bool2); + break; + } + + case LLVMInstruction::Type::Or: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + llvm::Value *bool1 = castValue(arg1.second, arg1.first); + llvm::Value *bool2 = castValue(arg2.second, arg2.first); + step.functionReturnReg->value = m_builder.CreateOr(bool1, bool2); + break; + } + + case LLVMInstruction::Type::Not: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + llvm::Value *value = castValue(arg.second, arg.first); + step.functionReturnReg->value = m_builder.CreateNot(value); + break; + } + + case LLVMInstruction::Type::Mod: { + assert(step.args.size() == 2); + const auto &arg1 = step.args[0]; + const auto &arg2 = step.args[1]; + // rem(a, b) / b < 0.0 ? rem(a, b) + b : rem(a, b) + llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); + llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); + llvm::Value *value = m_builder.CreateFRem(num1, num2); // rem(a, b) + llvm::Value *cond = m_builder.CreateFCmpOLT(m_builder.CreateFDiv(value, num2), zero); // rem(a, b) / b < 0.0 // rem(a, b) + step.functionReturnReg->value = m_builder.CreateSelect(cond, m_builder.CreateFAdd(value, num2), value); + break; + } + + case LLVMInstruction::Type::Round: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // x >= 0.0 ? round(x) : (x >= -0.5 ? -0.0 : floor(x + 0.5)) + llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Constant *negativeZero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-0.0)); + llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy()); + llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::floor, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + llvm::Value *notNegative = m_builder.CreateFCmpOGE(num, zero); // num >= 0.0 + llvm::Value *roundNum = m_builder.CreateCall(roundFunc, num); // round(num) + llvm::Value *negativeCond = m_builder.CreateFCmpOGE(num, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-0.5))); // num >= -0.5 + llvm::Value *negativeRound = m_builder.CreateCall(floorFunc, m_builder.CreateFAdd(num, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.5)))); // floor(x + 0.5) + step.functionReturnReg->value = m_builder.CreateSelect(notNegative, roundNum, m_builder.CreateSelect(negativeCond, negativeZero, negativeRound)); + break; + } + + case LLVMInstruction::Type::Abs: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + llvm::Function *absFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::fabs, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateCall(absFunc, num); + break; + } + + case LLVMInstruction::Type::Floor: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + llvm::Function *floorFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::floor, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateCall(floorFunc, num); + break; + } + + case LLVMInstruction::Type::Ceil: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + llvm::Function *ceilFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::ceil, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateCall(ceilFunc, num); + break; + } + + case LLVMInstruction::Type::Sqrt: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // sqrt(x) + 0.0 + // This avoids negative zero + llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Function *sqrtFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::sqrt, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateCall(sqrtFunc, num), zero); + break; + } + + case LLVMInstruction::Type::Sin: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // round(sin(x * pi / 180.0) * 1e10) / 1e10 + 0.0 + // +0.0 to avoid -0.0 + llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0))); + llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0)); + llvm::Constant *factor = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(1e10)); + llvm::Function *sinFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::sin, m_builder.getDoubleTy()); + llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + llvm::Value *sinResult = m_builder.CreateCall(sinFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // sin(x * pi / 180) + llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(sinResult, factor)); // round(sin(x * 180) * 1e10) + step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateFDiv(rounded, factor), zero); + break; + } + + case LLVMInstruction::Type::Cos: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // round(cos(x * pi / 180.0) * 1e10) / 1e10 + llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0))); + llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0)); + llvm::Constant *factor = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(1e10)); + llvm::Function *cosFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::cos, m_builder.getDoubleTy()); + llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + llvm::Value *cosResult = m_builder.CreateCall(cosFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // cos(x * pi / 180) + llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(cosResult, factor)); // round(cos(x * 180) * 1e10) + step.functionReturnReg->value = m_builder.CreateFDiv(rounded, factor); + break; + } + + case LLVMInstruction::Type::Tan: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // ((mod = rem(x, 360.0)) == -270.0 || mod == 90.0) ? inf : ((mod == -90.0 || mod == 270.0) ? -inf : round(tan(x * pi / 180.0) * 1e10) / 1e10 + 0.0) + // +0.0 to avoid -0.0 + llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Constant *full = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(360.0)); + llvm::Constant *posInf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false); + llvm::Constant *negInf = llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), true); + llvm::Constant *undefined1 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-270.0)); + llvm::Constant *undefined2 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(90.0)); + llvm::Constant *undefined3 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(-90.0)); + llvm::Constant *undefined4 = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(270.0)); + llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0))); + llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0)); + llvm::Constant *factor = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(1e10)); + llvm::Function *tanFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::tan, m_builder.getDoubleTy()); + llvm::Function *roundFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::round, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + llvm::Value *mod = m_builder.CreateFRem(num, full); + llvm::Value *isUndefined1 = m_builder.CreateFCmpOEQ(mod, undefined1); // rem(x, 360.0) == -270.0 + llvm::Value *isUndefined2 = m_builder.CreateFCmpOEQ(mod, undefined2); // rem(x, 360.0) == 90.0 + llvm::Value *isUndefined3 = m_builder.CreateFCmpOEQ(mod, undefined3); // rem(x, 360.0) == -90.0 + llvm::Value *isUndefined4 = m_builder.CreateFCmpOEQ(mod, undefined4); // rem(x, 360.0) == 270.0 + llvm::Value *tanResult = m_builder.CreateCall(tanFunc, m_builder.CreateFDiv(m_builder.CreateFMul(num, pi), piDeg)); // tan(x * pi / 180) + llvm::Value *rounded = m_builder.CreateCall(roundFunc, m_builder.CreateFMul(tanResult, factor)); // round(tan(x * 180) * 1e10) + llvm::Value *result = m_builder.CreateFAdd(m_builder.CreateFDiv(rounded, factor), zero); // round(tan(x * pi / 180.0) * 1e10) / 1e10 + 0.0 + llvm::Value *inner = m_builder.CreateSelect(m_builder.CreateOr(isUndefined3, isUndefined4), negInf, result); + step.functionReturnReg->value = m_builder.CreateSelect(m_builder.CreateOr(isUndefined1, isUndefined2), posInf, inner); + break; + } + + case LLVMInstruction::Type::Asin: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // asin(x) * 180.0 / pi + 0.0 + // +0.0 to avoid -0.0 + llvm::Constant *zero = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0))); + llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0)); + llvm::Function *asinFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::asin, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateFAdd(m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(asinFunc, num), piDeg), pi), zero); + break; + } + + case LLVMInstruction::Type::Acos: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // acos(x) * 180.0 / pi + llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0))); + llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0)); + llvm::Function *acosFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::acos, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(acosFunc, num), piDeg), pi); + break; + } + + case LLVMInstruction::Type::Atan: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // atan(x) * 180.0 / pi + llvm::Constant *pi = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(std::acos(-1.0))); + llvm::Constant *piDeg = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(180.0)); + llvm::Function *atanFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::atan, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateFDiv(m_builder.CreateFMul(m_builder.CreateCall(atanFunc, num), piDeg), pi); + break; + } + + case LLVMInstruction::Type::Ln: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // log(x) + llvm::Function *logFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::log, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateCall(logFunc, num); + break; + } + + case LLVMInstruction::Type::Log10: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // log10(x) + llvm::Function *log10Func = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::log10, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateCall(log10Func, num); + break; + } + + case LLVMInstruction::Type::Exp: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // exp(x) + llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::exp, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateCall(expFunc, num); + break; + } + + case LLVMInstruction::Type::Exp10: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + // exp10(x) + llvm::Function *expFunc = llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::exp10, m_builder.getDoubleTy()); + llvm::Value *num = removeNaN(castValue(arg.second, arg.first)); + step.functionReturnReg->value = m_builder.CreateCall(expFunc, num); + break; + } + + case LLVMInstruction::Type::Select: { + assert(step.args.size() == 3); + 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; + 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 = addAlloca(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()); + const auto &arg = step.args[0]; + Compiler::StaticType type = optimizeRegisterType(arg.second); + LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; + varPtr.changed = true; + + const bool safe = isVarOrListTypeSafe(insPtr, varPtr.type); + + // Initialize stack variable on first assignment + if (!varPtr.onStack) { + varPtr.onStack = true; + varPtr.type = type; // don't care about unknown type on first assignment + + ValueType mappedType; + + if (type == Compiler::StaticType::String || type == Compiler::StaticType::Unknown) { + // Value functions are used for these types, so don't break them + mappedType = ValueType::Number; + } else { + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [type](const std::pair &pair) { return pair.second == type; }); + assert(it != TYPE_MAP.cend()); + mappedType = it->first; + } + + llvm::Value *typeField = m_builder.CreateStructGEP(m_valueDataType, varPtr.stackPtr, 1); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typeField); + } + + if (!safe) + varPtr.type = Compiler::StaticType::Unknown; + + createValueStore(arg.second, varPtr.stackPtr, type, varPtr.type); + varPtr.type = type; + m_scopeVariables.back()[&varPtr] = varPtr.type; + break; + } + + case LLVMInstruction::Type::ReadVariable: { + assert(step.args.size() == 0); + LLVMVariablePtr &varPtr = m_variablePtrs[step.workVariable]; + + if (!isVarOrListTypeSafe(insPtr, varPtr.type)) + varPtr.type = Compiler::StaticType::Unknown; + + step.functionReturnReg->value = varPtr.onStack && !(step.loopCondition && !m_warp) ? varPtr.stackPtr : varPtr.heapPtr; + step.functionReturnReg->setType(varPtr.type); + break; + } + + case LLVMInstruction::Type::ClearList: { + assert(step.args.size() == 0); + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); + m_builder.CreateCall(resolve_list_clear(), listPtr.ptr); + + // Clearing may deallocate, so check if the allocated size changed + llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty); + llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); + m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpNE(allocatedSize, oldAllocatedSize)), listPtr.dataPtrDirty); + + m_scopeLists.back().erase(&listPtr); + break; + } + + case LLVMInstruction::Type::RemoveListItem: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + + // Range check + llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy()); + llvm::Value *index = castValue(arg.second, arg.first); + llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLT(index, size)); + llvm::BasicBlock *removeBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.CreateCondBr(inRange, removeBlock, nextBlock); + + // Remove + m_builder.SetInsertPoint(removeBlock); + index = m_builder.CreateFPToUI(castValue(arg.second, arg.first), m_builder.getInt64Ty()); + m_builder.CreateCall(resolve_list_remove(), { listPtr.ptr, index }); + // NOTE: Removing doesn't deallocate (see List::removeAt()), so there's no need to update the data pointer + m_builder.CreateBr(nextBlock); + + m_builder.SetInsertPoint(nextBlock); + break; + } + + case LLVMInstruction::Type::AppendToList: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + Compiler::StaticType type = optimizeRegisterType(arg.second); + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + auto &typeMap = m_scopeLists.back(); + + if (typeMap.find(&listPtr) == typeMap.cend()) { + listPtr.type = type; + typeMap[&listPtr] = listPtr.type; + } else if (listPtr.type != type) { + listPtr.type = Compiler::StaticType::Unknown; + typeMap[&listPtr] = listPtr.type; + } + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + + // Check if enough space is allocated + llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + llvm::Value *isAllocated = m_builder.CreateICmpUGT(allocatedSize, size); + llvm::BasicBlock *ifBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *elseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.CreateCondBr(isAllocated, ifBlock, elseBlock); + + // If there's enough space, use the allocated memory + m_builder.SetInsertPoint(ifBlock); + llvm::Value *itemPtr = getListItem(listPtr, size); + createReusedValueStore(arg.second, itemPtr, type, listPtr.type); + m_builder.CreateStore(m_builder.CreateAdd(size, m_builder.getInt64(1)), listPtr.sizePtr); + m_builder.CreateBr(nextBlock); + + // Otherwise call appendEmpty() + m_builder.SetInsertPoint(elseBlock); + itemPtr = m_builder.CreateCall(resolve_list_append_empty(), listPtr.ptr); + createReusedValueStore(arg.second, itemPtr, type, listPtr.type); + m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty); + m_builder.CreateBr(nextBlock); + + m_builder.SetInsertPoint(nextBlock); + + break; + } + + case LLVMInstruction::Type::InsertToList: { + assert(step.args.size() == 2); + const auto &indexArg = step.args[0]; + const auto &valueArg = step.args[1]; + Compiler::StaticType type = optimizeRegisterType(valueArg.second); + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + auto &typeMap = m_scopeLists.back(); + + if (typeMap.find(&listPtr) == typeMap.cend()) { + listPtr.type = type; + typeMap[&listPtr] = listPtr.type; + } else if (listPtr.type != type) { + listPtr.type = Compiler::StaticType::Unknown; + typeMap[&listPtr] = listPtr.type; + } + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + + llvm::Value *oldAllocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); + + // Range check + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy()); + llvm::Value *index = castValue(indexArg.second, indexArg.first); + llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLE(index, size)); + llvm::BasicBlock *insertBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.CreateCondBr(inRange, insertBlock, nextBlock); + + // Insert + m_builder.SetInsertPoint(insertBlock); + index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty()); + llvm::Value *itemPtr = m_builder.CreateCall(resolve_list_insert_empty(), { listPtr.ptr, index }); + createReusedValueStore(valueArg.second, itemPtr, type, listPtr.type); + + // Check if the allocated size changed + llvm::Value *dataPtrDirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty); + llvm::Value *allocatedSize = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.allocatedSizePtr); + m_builder.CreateStore(m_builder.CreateOr(dataPtrDirty, m_builder.CreateICmpNE(allocatedSize, oldAllocatedSize)), listPtr.dataPtrDirty); + + m_builder.CreateBr(nextBlock); + + m_builder.SetInsertPoint(nextBlock); + break; + } + + case LLVMInstruction::Type::ListReplace: { + assert(step.args.size() == 2); + const auto &indexArg = step.args[0]; + const auto &valueArg = step.args[1]; + Compiler::StaticType type = optimizeRegisterType(valueArg.second); + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + + // Range check + llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy()); + llvm::Value *index = castValue(indexArg.second, indexArg.first); + llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLT(index, size)); + llvm::BasicBlock *replaceBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.CreateCondBr(inRange, replaceBlock, nextBlock); + + // Replace + m_builder.SetInsertPoint(replaceBlock); + index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty()); + llvm::Value *itemPtr = getListItem(listPtr, index); + createValueStore(valueArg.second, itemPtr, type, listPtr.type); + m_builder.CreateBr(nextBlock); + + auto &typeMap = m_scopeLists.back(); + + if (typeMap.find(&listPtr) == typeMap.cend()) { + listPtr.type = type; + typeMap[&listPtr] = listPtr.type; + } else if (listPtr.type != type) { + listPtr.type = Compiler::StaticType::Unknown; + typeMap[&listPtr] = listPtr.type; + } + + m_builder.SetInsertPoint(nextBlock); + break; + } + + case LLVMInstruction::Type::GetListContents: { + assert(step.args.size() == 0); + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *ptr = m_builder.CreateCall(resolve_list_to_string(), listPtr.ptr); + freeLater(ptr); + step.functionReturnReg->value = ptr; + break; + } + + case LLVMInstruction::Type::GetListItem: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + + llvm::Value *min = llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)); + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + size = m_builder.CreateUIToFP(size, m_builder.getDoubleTy()); + llvm::Value *index = castValue(arg.second, arg.first); + llvm::Value *inRange = m_builder.CreateAnd(m_builder.CreateFCmpOGE(index, min), m_builder.CreateFCmpOLT(index, size)); + + LLVMConstantRegister nullReg(listPtr.type == Compiler::StaticType::Unknown ? Compiler::StaticType::Number : listPtr.type, Value()); + llvm::Value *null = createValue(static_cast(static_cast(&nullReg))); + + index = m_builder.CreateFPToUI(index, m_builder.getInt64Ty()); + step.functionReturnReg->value = m_builder.CreateSelect(inRange, getListItem(listPtr, index), null); + step.functionReturnReg->setType(listPtr.type); + break; + } + + case LLVMInstruction::Type::GetListSize: { + assert(step.args.size() == 0); + const LLVMListPtr &listPtr = m_listPtrs[step.workList]; + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + step.functionReturnReg->value = m_builder.CreateUIToFP(size, m_builder.getDoubleTy()); + break; + } + + case LLVMInstruction::Type::GetListItemIndex: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + + step.functionReturnReg->value = m_builder.CreateSIToFP(getListItemIndex(listPtr, arg.second), m_builder.getDoubleTy()); + break; + } + + case LLVMInstruction::Type::ListContainsItem: { + assert(step.args.size() == 1); + const auto &arg = step.args[0]; + LLVMListPtr &listPtr = m_listPtrs[step.workList]; + + if (!isVarOrListTypeSafe(insPtr, listPtr.type)) + listPtr.type = Compiler::StaticType::Unknown; + + llvm::Value *index = getListItemIndex(listPtr, arg.second); + step.functionReturnReg->value = m_builder.CreateICmpSGT(index, llvm::ConstantInt::get(m_builder.getInt64Ty(), -1, true)); + break; + } + + case LLVMInstruction::Type::Yield: + // TODO: Do not allow use after suspend (use after free) + createSuspend(coro.get(), warpArg, targetVariables); + break; + + case LLVMInstruction::Type::BeginIf: { + LLVMIfStatement statement; + statement.beforeIf = m_builder.GetInsertBlock(); + statement.body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + // Use last reg + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Bool); + statement.condition = castValue(reg.second, reg.first); + + // Switch to body branch + m_builder.SetInsertPoint(statement.body); + + ifStatements.push_back(statement); + pushScopeLevel(); + break; + } + + case LLVMInstruction::Type::BeginElse: { + assert(!ifStatements.empty()); + LLVMIfStatement &statement = ifStatements.back(); + + // Restore types from parent scope + std::unordered_map parentScopeVariables = m_scopeVariables[m_scopeVariables.size() - 2]; // no reference! + popScopeLevel(); + + for (auto &[ptr, type] : parentScopeVariables) + ptr->type = type; + + pushScopeLevel(); + + // Jump to the branch after the if statement + assert(!statement.afterIf); + statement.afterIf = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + freeScopeHeap(); + m_builder.CreateBr(statement.afterIf); + + // Create else branch + assert(!statement.elseBranch); + statement.elseBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + // 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 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_llvmCtx, "", m_function); + + 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(); + popScopeLevel(); + break; + } + + case LLVMInstruction::Type::BeginRepeatLoop: { + LLVMLoop loop; + loop.isRepeatLoop = true; + + // index = 0 + llvm::Constant *zero = llvm::ConstantInt::get(m_builder.getInt64Ty(), 0, true); + loop.index = addAlloca(m_builder.getInt64Ty()); + m_builder.CreateStore(zero, loop.index); + + // Create branches + llvm::BasicBlock *roundBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + // Use last reg for count + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Number); + llvm::Value *count = castValue(reg.second, reg.first); + llvm::Value *isInf = m_builder.CreateFCmpOEQ(count, llvm::ConstantFP::getInfinity(m_builder.getDoubleTy(), false)); + + // 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_llvmCtx, 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, llvm::Intrinsic::round, { count->getType() }); + count = m_builder.CreateCall(roundFunc, { count }); + count = m_builder.CreateFPToUI(count, m_builder.getInt64Ty()); // cast to unsigned integer + count = m_builder.CreateSelect(isInf, zero, count); + + // Jump to condition branch + m_builder.CreateBr(loop.conditionBranch); + + // Check index + m_builder.SetInsertPoint(loop.conditionBranch); + + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + if (!loop.afterLoop) + loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + llvm::Value *currentIndex = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index); + comparison = m_builder.CreateOr(isInf, m_builder.CreateICmpULT(currentIndex, count)); + m_builder.CreateCondBr(comparison, body, loop.afterLoop); + + // Switch to body branch + m_builder.SetInsertPoint(body); + + loops.push_back(loop); + pushScopeLevel(); + pushLoopScope(true); + break; + } + + case LLVMInstruction::Type::LoopIndex: { + assert(!loops.empty()); + LLVMLoop &loop = loops.back(); + llvm::Value *index = m_builder.CreateLoad(m_builder.getInt64Ty(), loop.index); + step.functionReturnReg->value = m_builder.CreateUIToFP(index, m_builder.getDoubleTy()); + break; + } + + case LLVMInstruction::Type::BeginWhileLoop: { + assert(!loops.empty()); + LLVMLoop &loop = loops.back(); + + // Create branches + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + // Use last reg + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Bool); + llvm::Value *condition = castValue(reg.second, reg.first); + m_builder.CreateCondBr(condition, body, loop.afterLoop); + + // Switch to body branch + m_builder.SetInsertPoint(body); + pushScopeLevel(); + pushLoopScope(true); + break; + } + + case LLVMInstruction::Type::BeginRepeatUntilLoop: { + assert(!loops.empty()); + LLVMLoop &loop = loops.back(); + + // Create branches + llvm::BasicBlock *body = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + loop.afterLoop = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + // Use last reg + assert(step.args.size() == 1); + const auto ® = step.args[0]; + assert(reg.first == Compiler::StaticType::Bool); + llvm::Value *condition = castValue(reg.second, reg.first); + m_builder.CreateCondBr(condition, loop.afterLoop, body); + + // Switch to body branch + m_builder.SetInsertPoint(body); + pushScopeLevel(); + pushLoopScope(true); + break; + } + + case LLVMInstruction::Type::BeginLoopCondition: { + LLVMLoop loop; + loop.isRepeatLoop = false; + loop.conditionBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.CreateBr(loop.conditionBranch); + m_builder.SetInsertPoint(loop.conditionBranch); + loops.push_back(loop); + break; + } + + case LLVMInstruction::Type::EndLoop: { + assert(!loops.empty()); + LLVMLoop &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, false)); + m_builder.CreateStore(incremented, loop.index); + } + + // Jump to the condition branch + freeScopeHeap(); + m_builder.CreateBr(loop.conditionBranch); + + // Switch to the branch after the loop + m_builder.SetInsertPoint(loop.afterLoop); + + loops.pop_back(); + popScopeLevel(); + popLoopScope(); + break; + } + + case LLVMInstruction::Type::Stop: { + m_builder.CreateBr(endBranch); + llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.SetInsertPoint(nextBranch); + break; + } + + case LLVMInstruction::Type::CallProcedure: { + assert(step.procedurePrototype); + assert(step.args.size() == step.procedurePrototype->argumentTypes().size()); + freeScopeHeap(); + syncVariables(targetVariables); + + std::string name = getMainFunctionName(step.procedurePrototype); + llvm::FunctionType *type = getMainFunctionType(step.procedurePrototype); + std::vector args; + + for (size_t i = 0; i < m_defaultArgCount; i++) + args.push_back(m_function->getArg(i)); + + // Add warp arg + if (m_warp) + args.push_back(m_builder.getInt1(true)); + else + args.push_back(m_procedurePrototype ? warpArg : m_builder.getInt1(false)); + + // Add procedure args + for (const auto &arg : step.args) { + if (arg.first == Compiler::StaticType::Unknown) + args.push_back(createValue(arg.second)); + else + args.push_back(castValue(arg.second, arg.first)); + } + + llvm::Value *handle = m_builder.CreateCall(resolveFunction(name, type), args); + + if (!m_warp && !step.procedurePrototype->warp()) { + llvm::BasicBlock *suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.CreateCondBr(m_builder.CreateIsNull(handle), nextBranch, suspendBranch); + + m_builder.SetInsertPoint(suspendBranch); + createSuspend(coro.get(), warpArg, targetVariables); + name = getResumeFunctionName(step.procedurePrototype); + llvm::Value *done = m_builder.CreateCall(resolveFunction(name, m_resumeFuncType), { handle }); + m_builder.CreateCondBr(done, nextBranch, suspendBranch); + + m_builder.SetInsertPoint(nextBranch); + } + + reloadVariables(targetVariables); + reloadLists(); + break; + } + + case LLVMInstruction::Type::ProcedureArg: { + assert(m_procedurePrototype); + llvm::Value *arg = m_function->getArg(m_defaultArgCount + 1 + step.procedureArgIndex); // omit warp arg + step.functionReturnReg->value = arg; + break; + } + } + } + + m_builder.CreateBr(endBranch); + + m_builder.SetInsertPoint(endBranch); + assert(m_heap.size() == 1); + freeScopeHeap(); + syncVariables(targetVariables); + + // End and verify the function + llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + + if (m_warp) + m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType)); + else + coro->end(); + + verifyFunction(m_function); + + // Create resume function + // bool resume(void *) + funcName = getResumeFunctionName(m_procedurePrototype); + llvm::Function *resumeFunc = getOrCreateFunction(funcName, m_resumeFuncType); + resumeFunc->addFnAttr(llvm::Attribute::NoInline); + resumeFunc->addFnAttr(llvm::Attribute::OptimizeNone); + + entry = llvm::BasicBlock::Create(m_llvmCtx, "entry", resumeFunc); + m_builder.SetInsertPoint(entry); + + if (m_warp) + m_builder.CreateRet(m_builder.getInt1(true)); + else + m_builder.CreateRet(coro->createResume(resumeFunc->getArg(0))); + + verifyFunction(resumeFunc); + + return std::make_shared(m_ctx, m_function->getName().str(), resumeFunc->getName().str()); +} + +CompilerValue *LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +{ + assert(argTypes.size() == args.size()); + + auto ins = std::make_shared(LLVMInstruction::Type::FunctionCall, currentLoopScope(), m_loopCondition); + ins->functionName = functionName; + + for (size_t i = 0; i < args.size(); i++) + ins->args.push_back({ argTypes[i], static_cast(args[i]) }); + + if (returnType != Compiler::StaticType::Void) { + auto reg = std::make_shared(returnType); + reg->isRawValue = true; + ins->functionReturnReg = reg.get(); + m_instructions.push_back(ins); + return addReg(reg, ins); + } + + m_instructions.push_back(ins); + return nullptr; +} + +CompilerValue *LLVMCodeBuilder::addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +{ + CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args); + m_instructions.back()->functionTargetArg = true; + return ret; +} + +CompilerValue *LLVMCodeBuilder::addFunctionCallWithCtx(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +{ + CompilerValue *ret = addFunctionCall(functionName, returnType, argTypes, args); + m_instructions.back()->functionCtxArg = true; + return ret; +} + +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(static_cast(addReg(reg, nullptr))); +} + +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) +{ + auto ins = std::make_shared(LLVMInstruction::Type::ReadVariable, currentLoopScope(), m_loopCondition); + ins->workVariable = variable; + + if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) + m_variablePtrs[variable] = LLVMVariablePtr(); + + auto ret = std::make_shared(Compiler::StaticType::Unknown); + ret->isRawValue = false; + ins->functionReturnReg = ret.get(); + + m_instructions.push_back(ins); + m_variableInstructions.push_back(m_instructions.back()); + return addReg(ret, ins); +} + +CompilerValue *LLVMCodeBuilder::addListContents(List *list) +{ + LLVMInstruction ins(LLVMInstruction::Type::GetListContents, currentLoopScope(), m_loopCondition); + ins.workList = list; + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + return createOp(ins, Compiler::StaticType::String); +} + +CompilerValue *LLVMCodeBuilder::addListItem(List *list, CompilerValue *index) +{ + auto ins = std::make_shared(LLVMInstruction::Type::GetListItem, currentLoopScope(), m_loopCondition); + ins->workList = list; + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + ins->args.push_back({ Compiler::StaticType::Number, static_cast(index) }); + + auto ret = std::make_shared(Compiler::StaticType::Unknown); + ret->isRawValue = false; + ins->functionReturnReg = ret.get(); + + m_instructions.push_back(ins); + m_listInstructions.push_back(m_instructions.back()); + return addReg(ret, ins); +} + +CompilerValue *LLVMCodeBuilder::addListItemIndex(List *list, CompilerValue *item) +{ + LLVMInstruction ins(LLVMInstruction::Type::GetListItemIndex, currentLoopScope(), m_loopCondition); + ins.workList = list; + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + auto ret = createOp(ins, Compiler::StaticType::Number, Compiler::StaticType::Unknown, { item }); + m_listInstructions.push_back(m_instructions.back()); + return ret; +} + +CompilerValue *LLVMCodeBuilder::addListContains(List *list, CompilerValue *item) +{ + LLVMInstruction ins(LLVMInstruction::Type::ListContainsItem, currentLoopScope(), m_loopCondition); + ins.workList = list; + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + auto ret = createOp(ins, Compiler::StaticType::Bool, Compiler::StaticType::Unknown, { item }); + m_listInstructions.push_back(m_instructions.back()); + return ret; +} + +CompilerValue *LLVMCodeBuilder::addListSize(List *list) +{ + LLVMInstruction ins(LLVMInstruction::Type::GetListSize, currentLoopScope(), m_loopCondition); + ins.workList = list; + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + return createOp(ins, Compiler::StaticType::Number); +} + +CompilerValue *LLVMCodeBuilder::addProcedureArgument(const std::string &name) +{ + if (!m_procedurePrototype) + return addConstValue(Value()); + + const auto &argNames = m_procedurePrototype->argumentNames(); + auto it = std::find(argNames.begin(), argNames.end(), name); + + if (it == argNames.end()) { + std::cout << "warning: could not find argument '" << name << "' in custom block '" << m_procedurePrototype->procCode() << "'" << std::endl; + return addConstValue(Value()); + } + + const auto index = it - argNames.begin(); + const Compiler::StaticType type = getProcedureArgType(m_procedurePrototype->argumentTypes()[index]); + auto ins = std::make_shared(LLVMInstruction::Type::ProcedureArg, currentLoopScope(), m_loopCondition); + auto ret = std::make_shared(type); + ret->isRawValue = (type != Compiler::StaticType::Unknown); + ins->functionReturnReg = ret.get(); + ins->procedureArgIndex = index; + + m_instructions.push_back(ins); + return addReg(ret, ins); +} + +CompilerValue *LLVMCodeBuilder::createAdd(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createSub(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::Sub, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createMul(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::Mul, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createDiv(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createRandom(CompilerValue *from, CompilerValue *to) +{ + 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 }); +} + +CompilerValue *LLVMCodeBuilder::createCmpGT(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::CmpGT, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createCmpLT(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive) +{ + return createOp(caseSensitive ? LLVMInstruction::Type::StrCmpEQCS : LLVMInstruction::Type::StrCmpEQCI, Compiler::StaticType::Bool, Compiler::StaticType::String, { string1, string2 }); +} + +CompilerValue *LLVMCodeBuilder::createAnd(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createOr(CompilerValue *operand1, CompilerValue *operand2) +{ + return createOp(LLVMInstruction::Type::Or, Compiler::StaticType::Bool, Compiler::StaticType::Bool, { operand1, operand2 }); +} + +CompilerValue *LLVMCodeBuilder::createNot(CompilerValue *operand) +{ + return createOp(LLVMInstruction::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, { operand }); +} + +CompilerValue *LLVMCodeBuilder::createMod(CompilerValue *num1, CompilerValue *num2) +{ + return createOp(LLVMInstruction::Type::Mod, Compiler::StaticType::Number, Compiler::StaticType::Number, { num1, num2 }); +} + +CompilerValue *LLVMCodeBuilder::createRound(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Round, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createAbs(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Abs, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createFloor(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Floor, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createCeil(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Ceil, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createSqrt(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Sqrt, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createSin(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Sin, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createCos(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Cos, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createTan(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Tan, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createAsin(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Asin, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createAcos(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Acos, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createAtan(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Atan, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createLn(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Ln, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createLog10(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Log10, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createExp(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Exp, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createExp10(CompilerValue *num) +{ + return createOp(LLVMInstruction::Type::Exp10, Compiler::StaticType::Number, Compiler::StaticType::Number, { num }); +} + +CompilerValue *LLVMCodeBuilder::createSelect(CompilerValue *cond, CompilerValue *trueValue, CompilerValue *falseValue, Compiler::StaticType valueType) +{ + 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) +{ + LLVMInstruction ins(LLVMInstruction::Type::WriteVariable, currentLoopScope(), m_loopCondition); + ins.workVariable = variable; + createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { value }); + + if (m_variablePtrs.find(variable) == m_variablePtrs.cend()) + m_variablePtrs[variable] = LLVMVariablePtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_variablePtrs[variable].loopVariableWrites[scope].push_back(m_instructions.back()); + } + + m_variableInstructions.push_back(m_instructions.back()); +} + +void LLVMCodeBuilder::createListClear(List *list) +{ + LLVMInstruction ins(LLVMInstruction::Type::ClearList, currentLoopScope(), m_loopCondition); + ins.workList = list; + createOp(ins, Compiler::StaticType::Void); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::createListRemove(List *list, CompilerValue *index) +{ + LLVMInstruction ins(LLVMInstruction::Type::RemoveListItem, currentLoopScope(), m_loopCondition); + ins.workList = list; + createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Number, { index }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); +} + +void LLVMCodeBuilder::createListAppend(List *list, CompilerValue *item) +{ + LLVMInstruction ins(LLVMInstruction::Type::AppendToList, currentLoopScope(), m_loopCondition); + ins.workList = list; + createOp(ins, Compiler::StaticType::Void, Compiler::StaticType::Unknown, { item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + + m_listInstructions.push_back(m_instructions.back()); +} + +void LLVMCodeBuilder::createListInsert(List *list, CompilerValue *index, CompilerValue *item) +{ + LLVMInstruction ins(LLVMInstruction::Type::InsertToList, currentLoopScope(), m_loopCondition); + ins.workList = list; + createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + + m_listInstructions.push_back(m_instructions.back()); +} + +void LLVMCodeBuilder::createListReplace(List *list, CompilerValue *index, CompilerValue *item) +{ + LLVMInstruction ins(LLVMInstruction::Type::ListReplace, currentLoopScope(), m_loopCondition); + ins.workList = list; + createOp(ins, Compiler::StaticType::Void, { Compiler::StaticType::Number, Compiler::StaticType::Unknown }, { index, item }); + + if (m_listPtrs.find(list) == m_listPtrs.cend()) + m_listPtrs[list] = LLVMListPtr(); + + if (m_loopScope >= 0) { + auto scope = m_loopScopes[m_loopScope]; + m_listPtrs[list].loopListWrites[scope].push_back(m_instructions.back()); + } + + m_listInstructions.push_back(m_instructions.back()); +} + +void LLVMCodeBuilder::beginIfStatement(CompilerValue *cond) +{ + auto ins = std::make_shared(LLVMInstruction::Type::BeginIf, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + m_instructions.push_back(ins); +} + +void LLVMCodeBuilder::beginElseBranch() +{ + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginElse, currentLoopScope(), m_loopCondition)); +} + +void LLVMCodeBuilder::endIf() +{ + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndIf, currentLoopScope(), m_loopCondition)); +} + +void LLVMCodeBuilder::beginRepeatLoop(CompilerValue *count) +{ + assert(!m_loopCondition); + + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatLoop, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Number, static_cast(count) }); + m_instructions.push_back(ins); + pushLoopScope(false); +} + +void LLVMCodeBuilder::beginWhileLoop(CompilerValue *cond) +{ + assert(m_loopCondition); + m_loopCondition = false; + + auto ins = std::make_shared(LLVMInstruction::Type::BeginWhileLoop, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + m_instructions.push_back(ins); + pushLoopScope(false); +} + +void LLVMCodeBuilder::beginRepeatUntilLoop(CompilerValue *cond) +{ + assert(m_loopCondition); + m_loopCondition = false; + + auto ins = std::make_shared(LLVMInstruction::Type::BeginRepeatUntilLoop, currentLoopScope(), m_loopCondition); + ins->args.push_back({ Compiler::StaticType::Bool, static_cast(cond) }); + m_instructions.push_back(ins); + pushLoopScope(false); +} + +void LLVMCodeBuilder::beginLoopCondition() +{ + assert(!m_loopCondition); + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::BeginLoopCondition, currentLoopScope(), m_loopCondition)); + m_loopCondition = true; +} + +void LLVMCodeBuilder::endLoop() +{ + if (!m_warp) + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition)); + + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::EndLoop, currentLoopScope(), m_loopCondition)); + popLoopScope(); +} + +void LLVMCodeBuilder::yield() +{ + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Yield, currentLoopScope(), m_loopCondition)); + + if (m_loopScope >= 0) + m_loopScopes[m_loopScope]->containsYield = true; +} + +void LLVMCodeBuilder::createStop() +{ + m_instructions.push_back(std::make_shared(LLVMInstruction::Type::Stop, currentLoopScope(), m_loopCondition)); +} + +void LLVMCodeBuilder::createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) +{ + assert(prototype); + assert(prototype->argumentTypes().size() == args.size()); + const auto &procedureArgs = prototype->argumentTypes(); + Compiler::ArgTypes types; + + for (BlockPrototype::ArgType type : procedureArgs) + types.push_back(getProcedureArgType(type)); + + LLVMInstruction ins(LLVMInstruction::Type::CallProcedure, currentLoopScope(), m_loopCondition); + ins.procedurePrototype = prototype; + createOp(ins, Compiler::StaticType::Void, types, args); +} + +void LLVMCodeBuilder::initTypes() +{ + llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + m_valueDataType = LLVMTypes::createValueDataType(&m_builder); + m_resumeFuncType = llvm::FunctionType::get(m_builder.getInt1Ty(), pointerType, false); +} + +void LLVMCodeBuilder::createVariableMap() +{ + if (!m_target) + return; + + // Map variable pointers to variable data array indices + const auto &variables = m_target->variables(); + ValueData **variableData = m_target->variableData(); + const size_t len = variables.size(); + m_targetVariableMap.clear(); + m_targetVariableMap.reserve(len); + + size_t i, j; + + for (i = 0; i < len; i++) { + Variable *var = variables[i].get(); + + // Find the data for this variable + for (j = 0; j < len; j++) { + if (variableData[j] == &var->valuePtr()->data()) + break; + } + + if (j < len) + m_targetVariableMap[var] = j; + else + assert(false); + } +} + +void LLVMCodeBuilder::createListMap() +{ + if (!m_target) + return; + + // Map list pointers to list array indices + const auto &lists = m_target->lists(); + List **listData = m_target->listData(); + const size_t len = lists.size(); + m_targetListMap.clear(); + m_targetListMap.reserve(len); + + size_t i, j; + + for (i = 0; i < len; i++) { + List *list = lists[i].get(); + + // Find this list + for (j = 0; j < len; j++) { + if (listData[j] == list) + break; + } + + if (j < len) + m_targetListMap[list] = j; + else + assert(false); + } +} + +void LLVMCodeBuilder::pushScopeLevel() +{ + m_scopeVariables.push_back({}); + + if (m_scopeLists.empty()) { + std::unordered_map listTypes; + + for (auto &[list, listPtr] : m_listPtrs) + listTypes[&listPtr] = Compiler::StaticType::Unknown; + + m_scopeLists.push_back(listTypes); + } else + m_scopeLists.push_back(m_scopeLists.back()); + + m_heap.push_back({}); +} + +void LLVMCodeBuilder::popScopeLevel() +{ + for (size_t i = 0; i < m_scopeVariables.size() - 1; i++) { + for (auto &[ptr, type] : m_scopeVariables[i]) { + if (ptr->type != type) + ptr->type = Compiler::StaticType::Unknown; + } + } + + m_scopeVariables.pop_back(); + + for (size_t i = 0; i < m_scopeLists.size() - 1; i++) { + for (auto &[ptr, type] : m_scopeLists[i]) { + if (ptr->type != type) + ptr->type = Compiler::StaticType::Unknown; + } + } + + m_scopeLists.pop_back(); + + freeScopeHeap(); + m_heap.pop_back(); +} + +void LLVMCodeBuilder::pushLoopScope(bool buildPhase) +{ + if (buildPhase) + m_loopScope = m_loopScopeCounter++; + else { + auto scope = std::make_shared(); + m_loopScopes.push_back(scope); + + if (m_loopScope >= 0) { + auto currentScope = m_loopScopes[m_loopScope]; + currentScope->childScopes.push_back(scope); + scope->parentScope = currentScope; + } + + m_loopScope = m_loopScopes.size() - 1; + } + + m_loopScopeTree.push_back(m_loopScope); +} + +void LLVMCodeBuilder::popLoopScope() +{ + m_loopScopeTree.pop_back(); + + if (m_loopScopeTree.empty()) { + m_loopScope = -1; + } else + m_loopScope = m_loopScopeTree.back(); +} + +std::string LLVMCodeBuilder::getMainFunctionName(BlockPrototype *procedurePrototype) +{ + return procedurePrototype ? "proc." + procedurePrototype->procCode() : "script"; +} + +std::string LLVMCodeBuilder::getResumeFunctionName(BlockPrototype *procedurePrototype) +{ + return procedurePrototype ? "resume.proc." + procedurePrototype->procCode() : "resume.script"; +} + +llvm::FunctionType *LLVMCodeBuilder::getMainFunctionType(BlockPrototype *procedurePrototype) +{ + // void *f(ExecutionContext *, Target *, ValueData **, List **, (warp arg), (procedure args...)) + llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + std::vector argTypes = { pointerType, pointerType, pointerType, pointerType }; + + if (procedurePrototype) { + argTypes.push_back(m_builder.getInt1Ty()); // warp arg (only in procedures) + const auto &types = procedurePrototype->argumentTypes(); + + for (BlockPrototype::ArgType type : types) { + if (type == BlockPrototype::ArgType::Bool) + argTypes.push_back(m_builder.getInt1Ty()); + else + argTypes.push_back(m_valueDataType->getPointerTo()); + } + } + + return llvm::FunctionType::get(pointerType, argTypes, false); +} + +llvm::Function *LLVMCodeBuilder::getOrCreateFunction(const std::string &name, llvm::FunctionType *type) +{ + llvm::Function *func = m_module->getFunction(name); + + if (func) + return func; + else + return llvm::Function::Create(type, llvm::Function::ExternalLinkage, name, m_module); +} + +void LLVMCodeBuilder::verifyFunction(llvm::Function *func) +{ + if (llvm::verifyFunction(*func, &llvm::errs())) { + llvm::errs() << "error: LLVM function verficiation failed!\n"; + llvm::errs() << "module name: " << m_module->getName() << "\n"; + } +} + +LLVMRegister *LLVMCodeBuilder::addReg(std::shared_ptr reg, std::shared_ptr ins) +{ + reg->instruction = ins; + m_regs.push_back(reg); + return reg.get(); +} + +llvm::Value *LLVMCodeBuilder::addAlloca(llvm::Type *type) +{ + // Add an alloca to the entry block because allocas must be there (to avoid stack overflow) + llvm::BasicBlock *block = m_builder.GetInsertBlock(); + m_builder.SetInsertPointPastAllocas(m_function); + llvm::Value *ret = m_builder.CreateAlloca(type); + m_builder.SetInsertPoint(block); + return ret; +} + +void LLVMCodeBuilder::freeLater(llvm::Value *value) +{ + assert(!m_heap.empty()); + + if (m_heap.empty()) + return; + + m_heap.back().push_back(value); +} + +void LLVMCodeBuilder::freeScopeHeap() +{ + 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); + + heap.clear(); +} + +llvm::Value *LLVMCodeBuilder::castValue(LLVMRegister *reg, Compiler::StaticType targetType) +{ + if (reg->isConst()) + return castConstValue(reg->constValue(), targetType); + + if (reg->isRawValue) + return castRawValue(reg, targetType); + + assert(reg->type() != Compiler::StaticType::Void && targetType != Compiler::StaticType::Void); + + switch (targetType) { + case Compiler::StaticType::Number: + switch (reg->type()) { + case Compiler::StaticType::Number: { + // Read number directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + return m_builder.CreateLoad(m_builder.getDoubleTy(), ptr); + } + + case Compiler::StaticType::Bool: { + // Read boolean and cast to double + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + llvm::Value *boolValue = m_builder.CreateLoad(m_builder.getInt1Ty(), ptr); + return m_builder.CreateSIToFP(boolValue, m_builder.getDoubleTy()); + } + + case Compiler::StaticType::String: + case Compiler::StaticType::Unknown: { + // Convert to double + return m_builder.CreateCall(resolve_value_toDouble(), reg->value); + } + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::Bool: + switch (reg->type()) { + case Compiler::StaticType::Number: { + // True if != 0 + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + llvm::Value *numberValue = m_builder.CreateLoad(m_builder.getDoubleTy(), ptr); + return m_builder.CreateFCmpONE(numberValue, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0))); + } + + case Compiler::StaticType::Bool: { + // Read boolean directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + return m_builder.CreateLoad(m_builder.getInt1Ty(), ptr); + } + + case Compiler::StaticType::String: + case Compiler::StaticType::Unknown: + // Convert to bool + return m_builder.CreateCall(resolve_value_toBool(), reg->value); + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::String: + switch (reg->type()) { + case Compiler::StaticType::Number: + case Compiler::StaticType::Bool: + case Compiler::StaticType::Unknown: { + // Cast to string + llvm::Value *ptr = m_builder.CreateCall(resolve_value_toCString(), reg->value); + freeLater(ptr); + return ptr; + } + + case Compiler::StaticType::String: { + // Read string pointer directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); + return m_builder.CreateLoad(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), ptr); + } + + default: + assert(false); + return nullptr; + } + + default: + assert(false); + return nullptr; + } +} + +llvm::Value *LLVMCodeBuilder::castRawValue(LLVMRegister *reg, Compiler::StaticType targetType) +{ + if (reg->type() == targetType) + return reg->value; + + switch (targetType) { + case Compiler::StaticType::Number: + switch (reg->type()) { + case Compiler::StaticType::Bool: + // Cast bool to double + return m_builder.CreateUIToFP(reg->value, m_builder.getDoubleTy()); + + case Compiler::StaticType::String: { + // Convert string to double + return m_builder.CreateCall(resolve_value_stringToDouble(), reg->value); + } + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::Bool: + switch (reg->type()) { + case Compiler::StaticType::Number: + // Cast double to bool (true if != 0) + return m_builder.CreateFCmpONE(reg->value, llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0))); + + case Compiler::StaticType::String: + // Convert string to bool + return m_builder.CreateCall(resolve_value_stringToBool(), reg->value); + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::String: + switch (reg->type()) { + case Compiler::StaticType::Number: { + // Convert double to string + llvm::Value *ptr = m_builder.CreateCall(resolve_value_doubleToCString(), reg->value); + freeLater(ptr); + return ptr; + } + + case Compiler::StaticType::Bool: { + // Convert bool to string + llvm::Value *ptr = m_builder.CreateCall(resolve_value_boolToCString(), reg->value); + // NOTE: Dot not deallocate later + return ptr; + } + + default: + assert(false); + return nullptr; + } + + default: + assert(false); + return nullptr; + } +} + +llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::StaticType targetType) +{ + switch (targetType) { + case Compiler::StaticType::Number: { + const double nan = std::numeric_limits::quiet_NaN(); + return llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(value.isNaN() ? nan : value.toDouble())); + } + + case Compiler::StaticType::Bool: + return m_builder.getInt1(value.toBool()); + + case Compiler::StaticType::String: + return m_builder.CreateGlobalStringPtr(value.toString()); + + default: + assert(false); + return nullptr; + } +} + +Compiler::StaticType LLVMCodeBuilder::optimizeRegisterType(LLVMRegister *reg) const +{ + Compiler::StaticType ret = reg->type(); + + // Optimize string constants that represent numbers + if (reg->isConst() && reg->type() == Compiler::StaticType::String && reg->constValue().isValidNumber()) + ret = Compiler::StaticType::Number; + + return ret; +} + +llvm::Type *LLVMCodeBuilder::getType(Compiler::StaticType type) +{ + switch (type) { + case Compiler::StaticType::Void: + return m_builder.getVoidTy(); + + case Compiler::StaticType::Number: + return m_builder.getDoubleTy(); + + case Compiler::StaticType::Bool: + return m_builder.getInt1Ty(); + + case Compiler::StaticType::String: + return llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + + default: + assert(false); + return nullptr; + } +} + +Compiler::StaticType LLVMCodeBuilder::getProcedureArgType(BlockPrototype::ArgType type) +{ + return type == BlockPrototype::ArgType::Bool ? Compiler::StaticType::Bool : Compiler::StaticType::Unknown; +} + +llvm::Value *LLVMCodeBuilder::isNaN(llvm::Value *num) +{ + return m_builder.CreateFCmpUNO(num, num); +} + +llvm::Value *LLVMCodeBuilder::removeNaN(llvm::Value *num) +{ + // Replace NaN with zero + return m_builder.CreateSelect(isNaN(num), llvm::ConstantFP::get(m_llvmCtx, llvm::APFloat(0.0)), num); +} + +llvm::Value *LLVMCodeBuilder::getVariablePtr(llvm::Value *targetVariables, Variable *variable) +{ + if (!m_target->isStage() && variable->target() == m_target) { + // If this is a local sprite variable, use the variable array at runtime (for clones) + assert(m_targetVariableMap.find(variable) != m_targetVariableMap.cend()); + const size_t index = m_targetVariableMap[variable]; + llvm::Value *ptr = m_builder.CreateGEP(m_valueDataType->getPointerTo(), targetVariables, m_builder.getInt64(index)); + return m_builder.CreateLoad(m_valueDataType->getPointerTo(), ptr); + } + + // Otherwise create a raw pointer at compile time + llvm::Value *addr = m_builder.getInt64((uintptr_t)&variable->value().data()); + return m_builder.CreateIntToPtr(addr, m_valueDataType->getPointerTo()); +} + +llvm::Value *LLVMCodeBuilder::getListPtr(llvm::Value *targetLists, List *list) +{ + if (!m_target->isStage() && list->target() == m_target) { + // If this is a local sprite list, use the list array at runtime (for clones) + assert(m_targetListMap.find(list) != m_targetListMap.cend()); + const size_t index = m_targetListMap[list]; + auto pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::Value *ptr = m_builder.CreateGEP(pointerType, targetLists, m_builder.getInt64(index)); + return m_builder.CreateLoad(pointerType, ptr); + } + + // Otherwise create a raw pointer at compile time + llvm::Value *addr = m_builder.getInt64((uintptr_t)list); + return m_builder.CreateIntToPtr(addr, m_valueDataType->getPointerTo()); +} + +void LLVMCodeBuilder::syncVariables(llvm::Value *targetVariables) +{ + // Copy stack variables to the actual variables + for (auto &[var, varPtr] : m_variablePtrs) { + if (varPtr.onStack && varPtr.changed) + createValueCopy(varPtr.stackPtr, getVariablePtr(targetVariables, var)); + + varPtr.changed = false; + } +} + +void LLVMCodeBuilder::reloadVariables(llvm::Value *targetVariables) +{ + // Reset variables to use heap + for (auto &[var, varPtr] : m_variablePtrs) { + varPtr.onStack = false; + varPtr.changed = false; + varPtr.type = Compiler::StaticType::Unknown; + } +} + +void LLVMCodeBuilder::reloadLists() +{ + // Reset list data dirty and list types + auto &typeMap = m_scopeLists.back(); + + for (auto &[list, listPtr] : m_listPtrs) { + m_builder.CreateStore(m_builder.getInt1(true), listPtr.dataPtrDirty); + listPtr.type = Compiler::StaticType::Unknown; + typeMap[&listPtr] = listPtr.type; + } +} + +void LLVMCodeBuilder::updateListDataPtr(const LLVMListPtr &listPtr) +{ + // dataPtr = dirty ? list_data(list) : dataPtr + // dirty = false + llvm::Value *dirty = m_builder.CreateLoad(m_builder.getInt1Ty(), listPtr.dataPtrDirty); + llvm::Value *dataPtr = m_builder.CreateSelect(dirty, m_builder.CreateCall(resolve_list_data(), listPtr.ptr), m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr)); + m_builder.CreateStore(dataPtr, listPtr.dataPtr); + m_builder.CreateStore(m_builder.getInt1(false), listPtr.dataPtrDirty); +} + +bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const +{ + std::unordered_set processed; + int counter = 0; + return isVarOrListTypeSafe(ins, expectedType, processed, counter); +} + +bool LLVMCodeBuilder::isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &log, int &c) const +{ + /* + * The main part of the loop type analyzer. + * + * This is a recursive function which is called when variable + * or list instruction is created. It checks the last write to + * the variable or list in one of the loop scopes. + * + * If the last write operation writes a value with a different + * type, it will return false, otherwise true. + * + * If the last written value is from a variable or list, this + * function is called for it to check its type safety (that's + * why it is recursive). + * + * If the variable or list had a write operation before (in + * the same, parent or child loop scope), it is checked + * recursively. + */ + + if (!ins) + return false; + + /* + * If we are processing something that has been already + * processed, it means there's a case like this: + * x = x + * + * or this: + * x = y + * ... + * y = x + * + * Increment counter to ignore last n write operations. + */ + if (log.find(ins.get()) != log.cend()) + c++; + else + log.insert(ins.get()); + + assert(std::find(m_instructions.begin(), m_instructions.end(), ins) != m_instructions.end()); + const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; + const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr; + assert((varPtr || listPtr) && !(varPtr && listPtr)); + auto scope = ins->loopScope; + + // If we aren't in a loop, we're safe + if (!scope) + return true; + + // If the loop scope contains a suspend and this is a non-warp script, the type may change between suspend and resume + if (scope->containsYield && !m_warp) + return false; + + std::shared_ptr write; + const auto &instructions = varPtr ? m_variableInstructions : m_listInstructions; + + // Find this instruction + auto it = std::find(instructions.begin(), instructions.end(), ins); + assert(it != instructions.end()); + + // Find previous write instruction in this, parent or child loop scope + size_t index = it - instructions.begin(); + + if (varPtr && index > 0) { // this is only needed for variables + bool found = false; + + do { + index--; + write = instructions[index]; + const bool isWrite = (VAR_LIST_READ_INSTRUCTIONS.find(write->type) == VAR_LIST_READ_INSTRUCTIONS.cend()); + found = (write->loopScope && isWrite && write->workVariable == ins->workVariable); + } while (index > 0 && !found); + + if (found) { + // Check if the write operation is in this or child scope + auto parentScope = write->loopScope; + + while (parentScope && parentScope != scope) + parentScope = parentScope->parentScope; + + if (!parentScope) { + // Check if the write operation is in any of the parent scopes + parentScope = scope; + + do { + parentScope = parentScope->parentScope; + } while (parentScope && parentScope != write->loopScope); + } + + // If there was a write operation before this instruction (in this, parent or child scope), check it + if (parentScope) { + if (parentScope == scope) + return isVarOrListWriteResultTypeSafe(write, expectedType, true, log, c); + else + return isVarOrListTypeSafe(write, expectedType, log, c); + } + } + } + + const auto &loopWrites = varPtr ? varPtr->loopVariableWrites : listPtr->loopListWrites; + + // Find root loop scope + auto checkScope = scope; + + while (checkScope->parentScope) { + checkScope = checkScope->parentScope; + } + + // Get all write operations in all loop scopes (from the root loop scope) + std::vector> lastWrites; + + while (checkScope) { + auto it = loopWrites.find(checkScope); + + if (it != loopWrites.cend()) { + assert(!it->second.empty()); + const auto &writes = it->second; + + for (auto w : writes) + lastWrites.push_back(w); + } + + if (checkScope->childScopes.empty()) + checkScope = nullptr; + else + checkScope = checkScope->childScopes.back(); + } + + // If there aren't any write operations or all of them are ignored, we're safe + if (c >= lastWrites.size()) + return true; + + if (varPtr) + write = lastWrites[lastWrites.size() - c - 1]; // Ignore last c writes + else { + // If this is a list instruction, check last write operations except current + for (long i = lastWrites.size() - c - 1; i >= 0; i--) { // Ignore last c writes + if (lastWrites[i] == ins) + continue; + + if (!isVarOrListWriteResultTypeSafe(lastWrites[i], expectedType, false, log, c)) + return false; + } + } + + bool safe = true; + + if (VAR_LIST_READ_INSTRUCTIONS.find(ins->type) == VAR_LIST_READ_INSTRUCTIONS.cend()) // write + safe = isVarOrListWriteResultTypeSafe(ins, expectedType, false, log, c); + + if (safe) + return write ? isVarOrListWriteResultTypeSafe(write, expectedType, false, log, c) : true; + else + return false; +} + +bool LLVMCodeBuilder::isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &log, int &c) + const +{ + const LLVMVariablePtr *varPtr = ins->workVariable ? &m_variablePtrs.at(ins->workVariable) : nullptr; + const LLVMListPtr *listPtr = ins->workList ? &m_listPtrs.at(ins->workList) : nullptr; + assert((varPtr || listPtr) && !(varPtr && listPtr)); + + // If the write operation writes the value of another variable, recursively check its type safety + const auto arg = ins->args.back().second; // value is always the last argument + auto argIns = arg->instruction; + + if (argIns && (argIns->type == LLVMInstruction::Type::ReadVariable || argIns->type == LLVMInstruction::Type::GetListItem)) + return isVarOrListTypeSafe(argIns, expectedType, log, c); + + // Check written type + const bool typeMatches = (optimizeRegisterType(arg) == expectedType); + + if (varPtr) + return typeMatches && (varPtr->type == expectedType || ignoreSavedType); + else + return typeMatches && (listPtr->type == expectedType || ignoreSavedType); +} + +LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) +{ + return createOp({ type, currentLoopScope(), m_loopCondition }, retType, argType, args); +} + +LLVMRegister *LLVMCodeBuilder::createOp(LLVMInstruction::Type type, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +{ + return createOp({ type, currentLoopScope(), m_loopCondition }, retType, argTypes, args); +} + +LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args) +{ + std::vector types; + types.reserve(args.size()); + + for (size_t i = 0; i < args.size(); i++) + types.push_back(argType); + + return createOp(ins, retType, types, args); +} + +LLVMRegister *LLVMCodeBuilder::createOp(const LLVMInstruction &ins, Compiler::StaticType retType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) +{ + auto createdIns = std::make_shared(ins); + m_instructions.push_back(createdIns); + + for (size_t i = 0; i < args.size(); i++) + createdIns->args.push_back({ argTypes[i], static_cast(args[i]) }); + + if (retType != Compiler::StaticType::Void) { + auto ret = std::make_shared(retType); + ret->isRawValue = true; + createdIns->functionReturnReg = ret.get(); + return addReg(ret, createdIns); + } + + return nullptr; +} + +std::shared_ptr LLVMCodeBuilder::currentLoopScope() const +{ + return m_loopScope >= 0 ? m_loopScopes[m_loopScope] : nullptr; +} + +void LLVMCodeBuilder::createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType) +{ + llvm::Value *converted = nullptr; + + if (sourceType != Compiler::StaticType::Unknown) + converted = castValue(reg, sourceType); + + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair &pair) { return pair.second == sourceType; }); + const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored + + switch (sourceType) { + case Compiler::StaticType::Number: + switch (targetType) { + case Compiler::StaticType::Number: { + // Write number to number directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + break; + } + + case Compiler::StaticType::Bool: { + // Write number to bool value directly and change type + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1); + m_builder.CreateStore(converted, ptr); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); + break; + } + + default: + m_builder.CreateCall(resolve_value_assign_double(), { targetPtr, converted }); + break; + } + + break; + + case Compiler::StaticType::Bool: + switch (targetType) { + case Compiler::StaticType::Number: { + // Write bool to number value directly and change type + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1); + m_builder.CreateStore(converted, ptr); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); + break; + } + + case Compiler::StaticType::Bool: { + // Write bool to bool directly + llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 0); + m_builder.CreateStore(converted, ptr); + break; + } + + default: + m_builder.CreateCall(resolve_value_assign_bool(), { targetPtr, converted }); + break; + } + + break; + + case Compiler::StaticType::String: + m_builder.CreateCall(resolve_value_assign_cstring(), { targetPtr, converted }); + break; + + case Compiler::StaticType::Unknown: + m_builder.CreateCall(resolve_value_assign_copy(), { targetPtr, reg->value }); + break; + + default: + assert(false); + break; + } +} + +void LLVMCodeBuilder::createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType) +{ + // Same as createValueStore(), but ensures that type is updated + createValueStore(reg, targetPtr, sourceType, targetType); + + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [sourceType](const std::pair &pair) { return pair.second == sourceType; }); + const ValueType mappedType = it == TYPE_MAP.cend() ? ValueType::Number : it->first; // unknown type can be ignored + + if ((sourceType == Compiler::StaticType::Number || sourceType == Compiler::StaticType::Bool) && sourceType == targetType) { + // Update type when writing number to number and bool to bool + llvm::Value *typePtr = m_builder.CreateStructGEP(m_valueDataType, targetPtr, 1); + m_builder.CreateStore(m_builder.getInt32(static_cast(mappedType)), typePtr); + } +} + +void LLVMCodeBuilder::createValueCopy(llvm::Value *source, llvm::Value *target) +{ + // NOTE: This doesn't copy strings, but only the pointers + copyStructField(source, target, 0, m_valueDataType, m_builder.getInt64Ty()); // value + copyStructField(source, target, 1, m_valueDataType, m_builder.getInt32Ty()); // type + /* 2: padding */ + copyStructField(source, target, 3, m_valueDataType, m_builder.getInt64Ty()); // string size +} + +void LLVMCodeBuilder::copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType) +{ + llvm::Value *sourceField = m_builder.CreateStructGEP(structType, source, index); + llvm::Value *targetField = m_builder.CreateStructGEP(structType, target, index); + m_builder.CreateStore(m_builder.CreateLoad(fieldType, sourceField), targetField); +} + +llvm::Value *LLVMCodeBuilder::getListItem(const LLVMListPtr &listPtr, llvm::Value *index) +{ + updateListDataPtr(listPtr); + return m_builder.CreateGEP(m_valueDataType, m_builder.CreateLoad(m_valueDataType->getPointerTo(), listPtr.dataPtr), index); +} + +llvm::Value *LLVMCodeBuilder::getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item) +{ + llvm::Value *size = m_builder.CreateLoad(m_builder.getInt64Ty(), listPtr.sizePtr); + llvm::BasicBlock *condBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *bodyBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *cmpIfBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *cmpElseBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *notFoundBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + llvm::BasicBlock *nextBlock = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + + // index = 0 + llvm::Value *index = addAlloca(m_builder.getInt64Ty()); + m_builder.CreateStore(m_builder.getInt64(0), index); + m_builder.CreateBr(condBlock); + + // while (index < size) + m_builder.SetInsertPoint(condBlock); + llvm::Value *cond = m_builder.CreateICmpULT(m_builder.CreateLoad(m_builder.getInt64Ty(), index), size); + m_builder.CreateCondBr(cond, bodyBlock, notFoundBlock); + + // if (list[index] == item) + m_builder.SetInsertPoint(bodyBlock); + LLVMRegister currentItem(listPtr.type); + currentItem.isRawValue = false; + currentItem.value = getListItem(listPtr, m_builder.CreateLoad(m_builder.getInt64Ty(), index)); + llvm::Value *cmp = createComparison(¤tItem, item, Comparison::EQ); + m_builder.CreateCondBr(cmp, cmpIfBlock, cmpElseBlock); + + // goto nextBlock + m_builder.SetInsertPoint(cmpIfBlock); + m_builder.CreateBr(nextBlock); + + // else index++ + m_builder.SetInsertPoint(cmpElseBlock); + m_builder.CreateStore(m_builder.CreateAdd(m_builder.CreateLoad(m_builder.getInt64Ty(), index), m_builder.getInt64(1)), index); + m_builder.CreateBr(condBlock); + + // notFoundBlock: + // index = -1 + // goto nextBlock + m_builder.SetInsertPoint(notFoundBlock); + m_builder.CreateStore(llvm::ConstantInt::get(llvm::Type::getInt64Ty(m_llvmCtx), -1, true), index); + m_builder.CreateBr(nextBlock); + + // nextBlock: + m_builder.SetInsertPoint(nextBlock); + + return m_builder.CreateLoad(m_builder.getInt64Ty(), index); +} + +llvm::Value *LLVMCodeBuilder::createValue(LLVMRegister *reg) +{ + if (reg->isConst()) { + // Create a constant ValueData instance and store it + llvm::Constant *value = castConstValue(reg->constValue(), TYPE_MAP[reg->constValue().type()]); + llvm::Value *ret = addAlloca(m_valueDataType); + + switch (reg->constValue().type()) { + case ValueType::Number: + value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0)); + break; + + case ValueType::Bool: + // Assuming union type is int64 + value = m_builder.getInt64(reg->constValue().toBool()); + break; + + case ValueType::String: + value = llvm::ConstantExpr::getPtrToInt(value, m_valueDataType->getElementType(0)); + break; + + default: + assert(false); + break; + } + + llvm::Constant *type = m_builder.getInt32(static_cast(reg->constValue().type())); + llvm::Constant *padding = m_builder.getInt32(0); + llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, padding, m_builder.getInt64(0) }); + m_builder.CreateStore(constValue, ret); + + return ret; + } else if (reg->isRawValue) { + llvm::Value *value = castRawValue(reg, reg->type()); + llvm::Value *ret = addAlloca(m_valueDataType); + + // Store value + llvm::Value *valueField = m_builder.CreateStructGEP(m_valueDataType, ret, 0); + m_builder.CreateStore(value, valueField); + + auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [®](const std::pair &pair) { return pair.second == reg->type(); }); + + if (it == TYPE_MAP.end()) { + assert(false); + return nullptr; + } + + // Store type + llvm::Value *typeField = m_builder.CreateStructGEP(m_valueDataType, ret, 1); + ValueType type = it->first; + m_builder.CreateStore(m_builder.getInt32(static_cast(type)), typeField); + + return ret; + } else + return reg->value; +} + +llvm::Value *LLVMCodeBuilder::createComparison(LLVMRegister *arg1, LLVMRegister *arg2, Comparison type) +{ + auto type1 = arg1->type(); + auto type2 = arg2->type(); + + if (arg1->isConst() && arg2->isConst()) { + // If both operands are constant, perform the comparison at compile time + bool result = false; + + switch (type) { + case Comparison::EQ: + result = arg1->constValue() == arg2->constValue(); + break; + + case Comparison::GT: + result = arg1->constValue() > arg2->constValue(); + break; + + case Comparison::LT: + result = arg1->constValue() < arg2->constValue(); + break; + + default: + assert(false); + return nullptr; + } + + return m_builder.getInt1(result); + } else { + // Optimize comparison of constant with number/bool + if (arg1->isConst() && arg1->constValue().isValidNumber() && (type2 == Compiler::StaticType::Number || type2 == Compiler::StaticType::Bool)) + type1 = Compiler::StaticType::Number; + + if (arg2->isConst() && arg2->constValue().isValidNumber() && (type1 == Compiler::StaticType::Number || type1 == Compiler::StaticType::Bool)) + type2 = Compiler::StaticType::Number; + + // Optimize number and bool comparison + int optNumberBool = 0; + + if (type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::Bool) { + type2 = Compiler::StaticType::Number; + optNumberBool = 2; // operand 2 was bool + } + + if (type1 == Compiler::StaticType::Bool && type2 == Compiler::StaticType::Number) { + type1 = Compiler::StaticType::Number; + optNumberBool = 1; // operand 1 was bool + } + + // Optimize number and string constant comparison + // TODO: GT and LT comparison can be optimized here (e. g. by checking the string constant characters and comparing with numbers and .+-e) + if (type == Comparison::EQ) { + if ((type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::String && arg2->isConst() && !arg2->constValue().isValidNumber()) || + (type1 == Compiler::StaticType::String && type2 == Compiler::StaticType::Number && arg1->isConst() && !arg1->constValue().isValidNumber())) + return m_builder.getInt1(false); + } + + if (type1 != type2 || type1 == Compiler::StaticType::Unknown || type2 == Compiler::StaticType::Unknown) { + // If the types are different or at least one of them + // is unknown, we must use value functions + llvm::Value *value1 = createValue(arg1); + llvm::Value *value2 = createValue(arg2); + + switch (type) { + case Comparison::EQ: + return m_builder.CreateCall(resolve_value_equals(), { value1, value2 }); + + case Comparison::GT: + return m_builder.CreateCall(resolve_value_greater(), { value1, value2 }); + + case Comparison::LT: + return m_builder.CreateCall(resolve_value_lower(), { value1, value2 }); + + default: + assert(false); + return nullptr; + } + } else { + // Compare raw values + llvm::Value *value1 = castValue(arg1, type1); + llvm::Value *value2 = castValue(arg2, type2); + assert(type1 == type2); + + switch (type1) { + case Compiler::StaticType::Number: { + // Compare two numbers + switch (type) { + case Comparison::EQ: { + llvm::Value *nan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN + llvm::Value *cmp = m_builder.CreateFCmpOEQ(value1, value2); + return m_builder.CreateSelect(nan, m_builder.getInt1(true), cmp); + } + + case Comparison::GT: { + llvm::Value *bothNan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN + llvm::Value *cmp = m_builder.CreateFCmpOGT(value1, value2); + llvm::Value *nan; + llvm::Value *nanCmp; + + if (optNumberBool == 1) { + nan = isNaN(value2); + nanCmp = castValue(arg1, Compiler::StaticType::Bool); + } else if (optNumberBool == 2) { + nan = isNaN(value1); + nanCmp = m_builder.CreateNot(castValue(arg2, Compiler::StaticType::Bool)); + } else { + nan = isNaN(value1); + nanCmp = m_builder.CreateFCmpUGT(value1, value2); + } + + return m_builder.CreateAnd(m_builder.CreateNot(bothNan), m_builder.CreateSelect(nan, nanCmp, cmp)); + } + + case Comparison::LT: { + llvm::Value *bothNan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN + llvm::Value *cmp = m_builder.CreateFCmpOLT(value1, value2); + llvm::Value *nan; + llvm::Value *nanCmp; + + if (optNumberBool == 1) { + nan = isNaN(value2); + nanCmp = m_builder.CreateNot(castValue(arg1, Compiler::StaticType::Bool)); + } else if (optNumberBool == 2) { + nan = isNaN(value1); + nanCmp = castValue(arg2, Compiler::StaticType::Bool); + } else { + nan = isNaN(value2); + nanCmp = m_builder.CreateFCmpULT(value1, value2); + } + + return m_builder.CreateAnd(m_builder.CreateNot(bothNan), m_builder.CreateSelect(nan, nanCmp, cmp)); + } + + default: + assert(false); + return nullptr; + } + } + + case Compiler::StaticType::Bool: + // Compare two booleans + switch (type) { + case Comparison::EQ: + return m_builder.CreateICmpEQ(value1, value2); + + case Comparison::GT: + // value1 && !value2 + return m_builder.CreateAnd(value1, m_builder.CreateNot(value2)); + + case Comparison::LT: + // value2 && !value1 + return m_builder.CreateAnd(value2, m_builder.CreateNot(value1)); + + default: + assert(false); + return nullptr; + } + + case Compiler::StaticType::String: { + // Compare two strings + llvm::Value *cmpRet = m_builder.CreateCall(resolve_strcasecmp(), { value1, value2 }); + + switch (type) { + case Comparison::EQ: + return m_builder.CreateICmpEQ(cmpRet, m_builder.getInt32(0)); + + case Comparison::GT: + return m_builder.CreateICmpSGT(cmpRet, m_builder.getInt32(0)); + + case Comparison::LT: + return m_builder.CreateICmpSLT(cmpRet, m_builder.getInt32(0)); + + default: + assert(false); + return nullptr; + } + } + + default: + assert(false); + return nullptr; + } + } + } +} + +llvm::Value *LLVMCodeBuilder::createStringComparison(LLVMRegister *arg1, LLVMRegister *arg2, bool caseSensitive) +{ + auto type1 = arg1->type(); + auto type2 = arg2->type(); + + if (arg1->isConst() && arg2->isConst()) { + // If both operands are constant, perform the comparison at compile time + bool result; + + if (caseSensitive) + result = arg1->constValue().toString() == arg2->constValue().toString(); + else { + std::string str1 = arg1->constValue().toString(); + std::string str2 = arg2->constValue().toString(); + result = strcasecmp(str1.c_str(), str2.c_str()) == 0; + } + + return m_builder.getInt1(result); + } else { + // Optimize number and string constant comparison + // TODO: Optimize bool and string constant comparison (in compare() as well) + if ((type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::String && arg2->isConst() && !arg2->constValue().isValidNumber()) || + (type1 == Compiler::StaticType::String && type2 == Compiler::StaticType::Number && arg1->isConst() && !arg1->constValue().isValidNumber())) + return m_builder.getInt1(false); + + // Explicitly cast to string + llvm::Value *string1 = castValue(arg1, Compiler::StaticType::String); + llvm::Value *string2 = castValue(arg2, Compiler::StaticType::String); + llvm::Value *cmp = m_builder.CreateCall(caseSensitive ? resolve_strcmp() : resolve_strcasecmp(), { string1, string2 }); + return m_builder.CreateICmpEQ(cmp, m_builder.getInt32(0)); + } +} + +void LLVMCodeBuilder::createSuspend(LLVMCoroutine *coro, llvm::Value *warpArg, llvm::Value *targetVariables) +{ + if (!m_warp) { + llvm::BasicBlock *suspendBranch, *nextBranch; + + if (warpArg) { + suspendBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + nextBranch = llvm::BasicBlock::Create(m_llvmCtx, "", m_function); + m_builder.CreateCondBr(warpArg, nextBranch, suspendBranch); + m_builder.SetInsertPoint(suspendBranch); + } + + freeScopeHeap(); + syncVariables(targetVariables); + coro->createSuspend(); + reloadVariables(targetVariables); + reloadLists(); + + if (warpArg) { + m_builder.CreateBr(nextBranch); + m_builder.SetInsertPoint(nextBranch); + } + } +} + +llvm::FunctionCallee LLVMCodeBuilder::resolveFunction(const std::string name, llvm::FunctionType *type) +{ + return m_module->getOrInsertFunction(name, type); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_init() +{ + return resolveFunction("value_init", llvm::FunctionType::get(m_builder.getVoidTy(), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_free() +{ + return resolveFunction("value_free", llvm::FunctionType::get(m_builder.getVoidTy(), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_long() +{ + return resolveFunction("value_assign_long", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt64Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_double() +{ + return resolveFunction("value_assign_double", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getDoubleTy() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_bool() +{ + return resolveFunction("value_assign_bool", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt1Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_cstring() +{ + return resolveFunction( + "value_assign_cstring", + llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0) }, false)); +} + +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_assign_copy() +{ + return resolveFunction("value_assign_copy", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_valueDataType->getPointerTo() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toDouble() +{ + llvm::FunctionCallee callee = resolveFunction("value_toDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), m_valueDataType->getPointerTo(), false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toBool() +{ + llvm::FunctionCallee callee = resolveFunction("value_toBool", llvm::FunctionType::get(m_builder.getInt1Ty(), m_valueDataType->getPointerTo(), false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toCString() +{ + // NOTE: This function can't be marked read-only because it allocates on the heap + return resolveFunction("value_toCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_valueDataType->getPointerTo(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_doubleToCString() +{ + // NOTE: This function can't be marked read-only because it allocates on the heap + return resolveFunction("value_doubleToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_builder.getDoubleTy(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_boolToCString() +{ + // NOTE: This function can't be marked read-only because it allocates on the heap + return resolveFunction("value_boolToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), m_builder.getInt1Ty(), false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToDouble() +{ + llvm::FunctionCallee callee = resolveFunction("value_stringToDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToBool() +{ + llvm::FunctionCallee callee = resolveFunction("value_stringToBool", llvm::FunctionType::get(m_builder.getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0), false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_equals() +{ + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + llvm::FunctionCallee callee = resolveFunction("value_equals", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_greater() +{ + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + llvm::FunctionCallee callee = resolveFunction("value_greater", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower() +{ + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + llvm::FunctionCallee callee = resolveFunction("value_lower", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_clear() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + return resolveFunction("list_clear", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_remove() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + return resolveFunction("list_remove", llvm::FunctionType::get(m_builder.getVoidTy(), { listPtr, m_builder.getInt64Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_append_empty() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + return resolveFunction("list_append_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_insert_empty() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + return resolveFunction("list_insert_empty", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr, m_builder.getInt64Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_data() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::FunctionCallee callee = resolveFunction("list_data", llvm::FunctionType::get(m_valueDataType->getPointerTo(), { listPtr }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_size_ptr() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::FunctionCallee callee = resolveFunction("list_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_alloc_size_ptr() +{ + llvm::Type *listPtr = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::FunctionCallee callee = resolveFunction("list_alloc_size_ptr", llvm::FunctionType::get(m_builder.getInt64Ty()->getPointerTo()->getPointerTo(), { listPtr }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_list_to_string() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::FunctionCallee callee = resolveFunction("list_to_string", llvm::FunctionType::get(pointerType, { pointerType }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::Type *valuePtr = m_valueDataType->getPointerTo(); + return resolveFunction("llvm_random", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, valuePtr, valuePtr }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_llvm_random_double() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + 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_llvmCtx), 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_llvmCtx), 0); + return resolveFunction("llvm_random_bool", llvm::FunctionType::get(m_builder.getDoubleTy(), { pointerType, m_builder.getInt1Ty(), m_builder.getInt1Ty() }, false)); +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_strcmp() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::FunctionCallee callee = resolveFunction("strcmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} + +llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp() +{ + llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_llvmCtx), 0); + llvm::FunctionCallee callee = resolveFunction("strcasecmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false)); + llvm::Function *func = llvm::cast(callee.getCallee()); + func->addFnAttr(llvm::Attribute::ReadOnly); + return callee; +} diff --git a/src/dev/engine/internal/llvm/llvmcodebuilder.h b/src/dev/engine/internal/llvm/llvmcodebuilder.h new file mode 100644 index 00000000..fb49fe64 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmcodebuilder.h @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include +#include +#include + +#include "../icodebuilder.h" +#include "llvminstruction.h" +#include "llvmcoroutine.h" +#include "llvmvariableptr.h" +#include "llvmlistptr.h" + +namespace libscratchcpp +{ + +class LLVMCompilerContext; +class LLVMConstantRegister; +class LLVMLoopScope; + +class LLVMCodeBuilder : public ICodeBuilder +{ + public: + LLVMCodeBuilder(LLVMCompilerContext *ctx, BlockPrototype *procedurePrototype = nullptr); + + std::shared_ptr finalize() override; + + CompilerValue *addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override; + CompilerValue *addTargetFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const Compiler::ArgTypes &argTypes, const Compiler::Args &args) override; + 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; + CompilerValue *addListItemIndex(List *list, CompilerValue *item) override; + CompilerValue *addListContains(List *list, CompilerValue *item) override; + CompilerValue *addListSize(List *list) override; + CompilerValue *addProcedureArgument(const std::string &name) override; + + CompilerValue *createAdd(CompilerValue *operand1, CompilerValue *operand2) override; + CompilerValue *createSub(CompilerValue *operand1, CompilerValue *operand2) override; + CompilerValue *createMul(CompilerValue *operand1, CompilerValue *operand2) override; + 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; + CompilerValue *createCmpLT(CompilerValue *operand1, CompilerValue *operand2) override; + + CompilerValue *createStrCmpEQ(CompilerValue *string1, CompilerValue *string2, bool caseSensitive = false) override; + + CompilerValue *createAnd(CompilerValue *operand1, CompilerValue *operand2) override; + CompilerValue *createOr(CompilerValue *operand1, CompilerValue *operand2) override; + CompilerValue *createNot(CompilerValue *operand) override; + + CompilerValue *createMod(CompilerValue *num1, CompilerValue *num2) override; + CompilerValue *createRound(CompilerValue *num) override; + CompilerValue *createAbs(CompilerValue *num) override; + CompilerValue *createFloor(CompilerValue *num) override; + CompilerValue *createCeil(CompilerValue *num) override; + CompilerValue *createSqrt(CompilerValue *num) override; + CompilerValue *createSin(CompilerValue *num) override; + CompilerValue *createCos(CompilerValue *num) override; + CompilerValue *createTan(CompilerValue *num) override; + CompilerValue *createAsin(CompilerValue *num) override; + CompilerValue *createAcos(CompilerValue *num) override; + CompilerValue *createAtan(CompilerValue *num) override; + CompilerValue *createLn(CompilerValue *num) override; + CompilerValue *createLog10(CompilerValue *num) override; + CompilerValue *createExp(CompilerValue *num) override; + CompilerValue *createExp10(CompilerValue *num) override; + + 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; + void createListRemove(List *list, CompilerValue *index) override; + void createListAppend(List *list, CompilerValue *item) override; + void createListInsert(List *list, CompilerValue *index, CompilerValue *item) override; + void createListReplace(List *list, CompilerValue *index, CompilerValue *item) override; + + void beginIfStatement(CompilerValue *cond) override; + void beginElseBranch() override; + void endIf() override; + + void beginRepeatLoop(CompilerValue *count) override; + void beginWhileLoop(CompilerValue *cond) override; + void beginRepeatUntilLoop(CompilerValue *cond) override; + void beginLoopCondition() override; + void endLoop() override; + + void yield() override; + + void createStop() override; + + void createProcedureCall(BlockPrototype *prototype, const Compiler::Args &args) override; + + private: + enum class Comparison + { + EQ, + GT, + LT + }; + + void initTypes(); + void createVariableMap(); + void createListMap(); + void pushScopeLevel(); + void popScopeLevel(); + void pushLoopScope(bool buildPhase); + void popLoopScope(); + + std::string getMainFunctionName(BlockPrototype *procedurePrototype); + std::string getResumeFunctionName(BlockPrototype *procedurePrototype); + llvm::FunctionType *getMainFunctionType(BlockPrototype *procedurePrototype); + llvm::Function *getOrCreateFunction(const std::string &name, llvm::FunctionType *type); + void verifyFunction(llvm::Function *func); + + LLVMRegister *addReg(std::shared_ptr reg, std::shared_ptr ins); + + llvm::Value *addAlloca(llvm::Type *type); + 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); + Compiler::StaticType optimizeRegisterType(LLVMRegister *reg) const; + llvm::Type *getType(Compiler::StaticType type); + Compiler::StaticType getProcedureArgType(BlockPrototype::ArgType type); + llvm::Value *isNaN(llvm::Value *num); + llvm::Value *removeNaN(llvm::Value *num); + + llvm::Value *getVariablePtr(llvm::Value *targetVariables, Variable *variable); + llvm::Value *getListPtr(llvm::Value *targetLists, List *list); + void syncVariables(llvm::Value *targetVariables); + void reloadVariables(llvm::Value *targetVariables); + void reloadLists(); + void updateListDataPtr(const LLVMListPtr &listPtr); + bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType) const; + bool isVarOrListTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, std::unordered_set &log, int &c) const; + bool isVarOrListWriteResultTypeSafe(std::shared_ptr ins, Compiler::StaticType expectedType, bool ignoreSavedType, std::unordered_set &log, int &c) const; + + LLVMRegister *createOp(LLVMInstruction::Type type, Compiler::StaticType retType, Compiler::StaticType argType, const Compiler::Args &args); + LLVMRegister *createOp(LLVMInstruction::Type type, 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 = {}); + std::shared_ptr currentLoopScope() const; + + void createValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); + void createReusedValueStore(LLVMRegister *reg, llvm::Value *targetPtr, Compiler::StaticType sourceType, Compiler::StaticType targetType); + void createValueCopy(llvm::Value *source, llvm::Value *target); + void copyStructField(llvm::Value *source, llvm::Value *target, int index, llvm::StructType *structType, llvm::Type *fieldType); + llvm::Value *getListItem(const LLVMListPtr &listPtr, llvm::Value *index); + llvm::Value *getListItemIndex(const LLVMListPtr &listPtr, LLVMRegister *item); + llvm::Value *createValue(LLVMRegister *reg); + llvm::Value *createComparison(LLVMRegister *arg1, LLVMRegister *arg2, Comparison type); + llvm::Value *createStringComparison(LLVMRegister *arg1, LLVMRegister *arg2, bool caseSensitive); + + void createSuspend(LLVMCoroutine *coro, llvm::Value *warpArg, llvm::Value *targetVariables); + + llvm::FunctionCallee resolveFunction(const std::string name, llvm::FunctionType *type); + llvm::FunctionCallee resolve_value_init(); + llvm::FunctionCallee resolve_value_free(); + llvm::FunctionCallee resolve_value_assign_long(); + llvm::FunctionCallee resolve_value_assign_double(); + llvm::FunctionCallee resolve_value_assign_bool(); + llvm::FunctionCallee resolve_value_assign_cstring(); + llvm::FunctionCallee resolve_value_assign_special(); + llvm::FunctionCallee resolve_value_assign_copy(); + llvm::FunctionCallee resolve_value_toDouble(); + llvm::FunctionCallee resolve_value_toBool(); + llvm::FunctionCallee resolve_value_toCString(); + llvm::FunctionCallee resolve_value_doubleToCString(); + llvm::FunctionCallee resolve_value_boolToCString(); + llvm::FunctionCallee resolve_value_stringToDouble(); + llvm::FunctionCallee resolve_value_stringToBool(); + llvm::FunctionCallee resolve_value_equals(); + llvm::FunctionCallee resolve_value_greater(); + llvm::FunctionCallee resolve_value_lower(); + llvm::FunctionCallee resolve_list_clear(); + llvm::FunctionCallee resolve_list_remove(); + llvm::FunctionCallee resolve_list_append_empty(); + llvm::FunctionCallee resolve_list_insert_empty(); + llvm::FunctionCallee resolve_list_data(); + llvm::FunctionCallee resolve_list_size_ptr(); + llvm::FunctionCallee resolve_list_alloc_size_ptr(); + 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_strcmp(); + llvm::FunctionCallee resolve_strcasecmp(); + + Target *m_target = nullptr; + + std::unordered_map m_targetVariableMap; + std::unordered_map m_variablePtrs; + std::vector> m_scopeVariables; + + std::unordered_map m_targetListMap; + std::unordered_map m_listPtrs; + std::vector> m_scopeLists; + + LLVMCompilerContext *m_ctx; + llvm::LLVMContext &m_llvmCtx; + llvm::Module *m_module = nullptr; + llvm::IRBuilder<> m_builder; + llvm::Function *m_function = nullptr; + + llvm::StructType *m_valueDataType = nullptr; + llvm::FunctionType *m_resumeFuncType = nullptr; + + std::vector> m_instructions; + std::vector> m_regs; + std::vector> m_localVars; + BlockPrototype *m_procedurePrototype = nullptr; + bool m_defaultWarp = false; + bool m_warp = false; + int m_defaultArgCount = 0; + + long m_loopScope = -1; // index + std::vector> m_loopScopes; + long m_loopScopeCounter = 0; // replacement for m_loopScopes size in build phase + std::vector m_loopScopeTree; + bool m_loopCondition = false; // whether we're currently compiling a loop condition + std::vector> m_variableInstructions; + std::vector> m_listInstructions; + std::vector> m_heap; // scopes + + std::shared_ptr m_output; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.cpp b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp new file mode 100644 index 00000000..bda5bab9 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmcompilercontext.cpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include + +#include "llvmcompilercontext.h" + +using namespace libscratchcpp; + +LLVMCompilerContext::LLVMCompilerContext(IEngine *engine, Target *target) : + CompilerContext(engine, target), + m_llvmCtx(std::make_unique()), + m_module(std::make_unique(target ? target->name() : "", *m_llvmCtx)), + m_llvmCtxPtr(m_llvmCtx.get()), + m_modulePtr(m_module.get()), + m_jit((initTarget(), llvm::orc::LLJITBuilder().create())) +{ + if (!m_jit) { + llvm::errs() << "error: failed to create JIT: " << toString(m_jit.takeError()) << "\n"; + return; + } +} + +void LLVMCompilerContext::preoptimize() +{ + initJit(); +} + +llvm::LLVMContext *LLVMCompilerContext::llvmCtx() +{ + return m_llvmCtxPtr; +} + +llvm::Module *LLVMCompilerContext::module() +{ + return m_modulePtr; +} + +void LLVMCompilerContext::initJit() +{ + if (m_jitInitialized) { + std::cout << "warning: JIT compiler is already initialized" << std::endl; + return; + } + + m_jitInitialized = true; + assert(m_llvmCtx); + assert(m_module); + +#ifdef PRINT_LLVM_IR + std::cout << std::endl << "=== LLVM IR (" << m_module->getName().str() << ") ===" << std::endl; + m_module->print(llvm::outs(), nullptr); + std::cout << "==============" << std::endl << std::endl; +#endif + + // Optimize + llvm::PassBuilder passBuilder; + llvm::LoopAnalysisManager loopAnalysisManager; + llvm::FunctionAnalysisManager functionAnalysisManager; + llvm::CGSCCAnalysisManager cGSCCAnalysisManager; + llvm::ModuleAnalysisManager moduleAnalysisManager; + + passBuilder.registerModuleAnalyses(moduleAnalysisManager); + passBuilder.registerCGSCCAnalyses(cGSCCAnalysisManager); + passBuilder.registerFunctionAnalyses(functionAnalysisManager); + passBuilder.registerLoopAnalyses(loopAnalysisManager); + passBuilder.crossRegisterProxies(loopAnalysisManager, functionAnalysisManager, cGSCCAnalysisManager, moduleAnalysisManager); + + llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3); + modulePassManager.run(*m_module, moduleAnalysisManager); + + const auto &functions = m_module->getFunctionList(); + std::vector lookupNames; + + for (const llvm::Function &func : functions) { + if (func.hasExternalLinkage() && !func.isDeclaration()) + lookupNames.push_back(func.getName().str()); + } + + // Init JIT compiler + std::string name = m_module->getName().str(); + auto err = m_jit->get()->addIRModule(llvm::orc::ThreadSafeModule(std::move(m_module), std::move(m_llvmCtx))); + + if (err) { + llvm::errs() << "error: failed to add module '" << name << "' to JIT: " << toString(std::move(err)) << "\n"; + return; + } + + // Lookup functions to JIT-compile ahead of time + for (const std::string &name : lookupNames) { +#ifndef NDEBUG + std::cout << "debug: looking up function: " << name << std::endl; +#endif + lookupFunction(name); + } +} + +bool LLVMCompilerContext::jitInitialized() const +{ + return m_jitInitialized; +} + +void LLVMCompilerContext::initTarget() +{ + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); +} diff --git a/src/dev/engine/internal/llvm/llvmcompilercontext.h b/src/dev/engine/internal/llvm/llvmcompilercontext.h new file mode 100644 index 00000000..7744c04d --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmcompilercontext.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +#include + +namespace libscratchcpp +{ + +class ExecutionContext; +class ValueData; +class List; +class LLVMExecutableCode; + +class LLVMCompilerContext : public CompilerContext +{ + public: + LLVMCompilerContext(IEngine *engine, Target *target); + + void preoptimize() override; + + llvm::LLVMContext *llvmCtx(); + llvm::Module *module(); + + void initJit(); + bool jitInitialized() const; + + template + T lookupFunction(const std::string &name) + { + auto func = m_jit->get()->lookup(name); + + if (func) + return (T)func->getValue(); + else { + llvm::errs() << "error: failed to lookup LLVM function: " << toString(func.takeError()) << "\n"; + return nullptr; + } + } + + private: + void initTarget(); + + std::unique_ptr m_llvmCtx; + std::unique_ptr m_module; + llvm::LLVMContext *m_llvmCtxPtr = nullptr; + llvm::Module *m_modulePtr = nullptr; + llvm::Expected> m_jit; + bool m_jitInitialized = false; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmconstantregister.h b/src/dev/engine/internal/llvm/llvmconstantregister.h new file mode 100644 index 00000000..94c0d229 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmconstantregister.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "llvmregister.h" + +namespace libscratchcpp +{ + +struct LLVMConstantRegister + : public LLVMRegisterBase + , public CompilerConstant +{ + LLVMConstantRegister(Compiler::StaticType type, const Value &value) : + LLVMRegisterBase(), + CompilerConstant(type, value) + { + } + + const Value &constValue() const override { return CompilerConstant::value(); } +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmcoroutine.cpp b/src/dev/engine/internal/llvm/llvmcoroutine.cpp new file mode 100644 index 00000000..17b834d9 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmcoroutine.cpp @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "llvmcoroutine.h" + +using namespace libscratchcpp; + +LLVMCoroutine::LLVMCoroutine(llvm::Module *module, llvm::IRBuilder<> *builder, llvm::Function *func) : + m_module(module), + m_builder(builder), + m_function(func) +{ + llvm::LLVMContext &ctx = builder->getContext(); + + // Set presplitcoroutine attribute + func->setPresplitCoroutine(); + + // Coroutine intrinsics + llvm::Function *coroId = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_id); + llvm::Function *coroSize = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_size, builder->getInt64Ty()); + llvm::Function *coroBegin = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_begin); + llvm::Function *coroEnd = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_end); + llvm::Function *coroFree = llvm::Intrinsic::getDeclaration(module, llvm::Intrinsic::coro_free); + + // Init coroutine + llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(ctx), 0); + llvm::Constant *nullPointer = llvm::ConstantPointerNull::get(pointerType); + llvm::Value *coroIdRet = builder->CreateCall(coroId, { builder->getInt32(8), nullPointer, nullPointer, nullPointer }); + + // Allocate memory + llvm::Value *coroSizeRet = builder->CreateCall(coroSize, std::nullopt, "size"); + llvm::FunctionCallee mallocFunc = module->getOrInsertFunction("malloc", llvm::FunctionType::get(pointerType, { builder->getInt64Ty() }, false)); + llvm::Value *alloc = builder->CreateCall(mallocFunc, coroSizeRet, "mem"); + + // Begin + m_handle = builder->CreateCall(coroBegin, { coroIdRet, alloc }); + m_didSuspendVar = builder->CreateAlloca(builder->getInt1Ty(), nullptr, "didSuspend"); + builder->CreateStore(builder->getInt1(false), m_didSuspendVar); + llvm::BasicBlock *entry = builder->GetInsertBlock(); + + // Create suspend branch + m_suspendBlock = llvm::BasicBlock::Create(ctx, "suspend", func); + builder->SetInsertPoint(m_suspendBlock); + builder->CreateCall(coroEnd, { m_handle, builder->getInt1(false), llvm::ConstantTokenNone::get(ctx) }); + builder->CreateRet(m_handle); + + // Create free branches + m_freeMemRetBlock = llvm::BasicBlock::Create(ctx, "freeMemRet", func); + builder->SetInsertPoint(m_freeMemRetBlock); + builder->CreateFree(alloc); + builder->CreateRet(llvm::ConstantPointerNull::get(pointerType)); + + llvm::BasicBlock *freeBranch = llvm::BasicBlock::Create(ctx, "free", func); + builder->SetInsertPoint(freeBranch); + builder->CreateFree(alloc); + builder->CreateBr(m_suspendBlock); + + // Create cleanup branch + m_cleanupBlock = llvm::BasicBlock::Create(ctx, "cleanup", func); + builder->SetInsertPoint(m_cleanupBlock); + llvm::Value *mem = builder->CreateCall(coroFree, { coroIdRet, m_handle }); + llvm::Value *needFree = builder->CreateIsNotNull(mem); + builder->CreateCondBr(needFree, freeBranch, m_suspendBlock); + + builder->SetInsertPoint(entry); +} + +llvm::Value *LLVMCoroutine::handle() const +{ + return m_handle; +} + +llvm::BasicBlock *LLVMCoroutine::suspendBlock() const +{ + return m_suspendBlock; +} + +llvm::BasicBlock *LLVMCoroutine::cleanupBlock() const +{ + return m_cleanupBlock; +} + +llvm::BasicBlock *LLVMCoroutine::freeMemRetBlock() const +{ + return m_freeMemRetBlock; +} + +llvm::Value *LLVMCoroutine::didSuspendVar() const +{ + return m_didSuspendVar; +} + +void LLVMCoroutine::createSuspend() +{ + llvm::LLVMContext &ctx = m_builder->getContext(); + + m_builder->CreateStore(m_builder->getInt1(true), m_didSuspendVar); + llvm::BasicBlock *resumeBranch = llvm::BasicBlock::Create(ctx, "", m_function); + llvm::Value *noneToken = llvm::ConstantTokenNone::get(ctx); + llvm::Value *suspendResult = m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_suspend), { noneToken, m_builder->getInt1(false) }); + llvm::SwitchInst *sw = m_builder->CreateSwitch(suspendResult, m_suspendBlock, 2); + sw->addCase(m_builder->getInt8(0), resumeBranch); + sw->addCase(m_builder->getInt8(1), m_cleanupBlock); + m_builder->SetInsertPoint(resumeBranch); +} + +llvm::Value *LLVMCoroutine::createResume(llvm::Value *coroHandle) +{ + m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_resume), { coroHandle }); + return m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_done), { coroHandle }); +} + +void LLVMCoroutine::end() +{ + llvm::LLVMContext &ctx = m_builder->getContext(); + + // Add final suspend point + llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(ctx, "end", m_function); + llvm::BasicBlock *finalSuspendBranch = llvm::BasicBlock::Create(ctx, "finalSuspend", m_function); + m_builder->CreateCondBr(m_builder->CreateLoad(m_builder->getInt1Ty(), m_didSuspendVar), finalSuspendBranch, endBranch); + + m_builder->SetInsertPoint(finalSuspendBranch); + llvm::Value *suspendResult = m_builder->CreateCall(llvm::Intrinsic::getDeclaration(m_module, llvm::Intrinsic::coro_suspend), { llvm::ConstantTokenNone::get(ctx), m_builder->getInt1(true) }); + llvm::SwitchInst *sw = m_builder->CreateSwitch(suspendResult, m_suspendBlock, 2); + sw->addCase(m_builder->getInt8(0), endBranch); // unreachable + sw->addCase(m_builder->getInt8(1), m_cleanupBlock); + + // Jump to "free and return" branch + m_builder->SetInsertPoint(endBranch); + m_builder->CreateBr(m_freeMemRetBlock); +} diff --git a/src/dev/engine/internal/llvm/llvmcoroutine.h b/src/dev/engine/internal/llvm/llvmcoroutine.h new file mode 100644 index 00000000..2c715896 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmcoroutine.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class LLVMCoroutine +{ + public: + LLVMCoroutine(llvm::Module *module, llvm::IRBuilder<> *builder, llvm::Function *func); + LLVMCoroutine(const LLVMCoroutine &) = delete; + + llvm::Value *handle() const; + + llvm::BasicBlock *suspendBlock() const; + llvm::BasicBlock *cleanupBlock() const; + llvm::BasicBlock *freeMemRetBlock() const; + + llvm::Value *didSuspendVar() const; + + void createSuspend(); + llvm::Value *createResume(llvm::Value *coroHandle); + void end(); + + private: + llvm::Module *m_module = nullptr; + llvm::IRBuilder<> *m_builder = nullptr; + llvm::Function *m_function = nullptr; + llvm::Value *m_handle = nullptr; + llvm::BasicBlock *m_suspendBlock = nullptr; + llvm::BasicBlock *m_cleanupBlock = nullptr; + llvm::BasicBlock *m_freeMemRetBlock = nullptr; + llvm::Value *m_didSuspendVar = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvmexecutablecode.cpp b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp similarity index 54% rename from src/dev/engine/internal/llvmexecutablecode.cpp rename to src/dev/engine/internal/llvm/llvmexecutablecode.cpp index 6c2059ba..ed88ac60 100644 --- a/src/dev/engine/internal/llvmexecutablecode.cpp +++ b/src/dev/engine/internal/llvm/llvmexecutablecode.cpp @@ -2,48 +2,51 @@ #include #include -#include +#include +#include +#include +#include #include +#include + #include "llvmexecutablecode.h" +#include "llvmcompilercontext.h" #include "llvmexecutioncontext.h" using namespace libscratchcpp; -LLVMExecutableCode::LLVMExecutableCode(std::unique_ptr module) : - m_ctx(std::make_unique()), - m_jit(llvm::orc::LLJITBuilder().create()) +LLVMExecutableCode::LLVMExecutableCode(LLVMCompilerContext *ctx, const std::string &mainFunctionName, const std::string &resumeFunctionName) : + m_ctx(ctx), + m_mainFunctionName(mainFunctionName), + m_resumeFunctionName(resumeFunctionName) { - if (!m_jit) { - llvm::errs() << "error: failed to create JIT: " << toString(m_jit.takeError()) << "\n"; - return; - } - - if (!module) - return; + assert(m_ctx); - std::string name = module->getName().str(); - auto err = m_jit->get()->addIRModule(llvm::orc::ThreadSafeModule(std::move(module), std::move(m_ctx))); - - if (err) { - llvm::errs() << "error: failed to add module '" << name << "' to JIT: " << toString(std::move(err)) << "\n"; - return; + if (m_ctx->jitInitialized()) { + std::cerr << "error: cannot create LLVM code after JIT compiler had been initialized" << std::endl; + assert(false); } - - // Lookup functions - m_mainFunction = (MainFunctionType)lookupFunction("f"); - assert(m_mainFunction); - m_resumeFunction = (ResumeFunctionType)lookupFunction("resume"); - assert(m_resumeFunction); } void LLVMExecutableCode::run(ExecutionContext *context) { + assert(m_mainFunction); + assert(m_resumeFunction); LLVMExecutionContext *ctx = getContext(context); if (ctx->finished()) return; + auto promise = ctx->promise(); + + if (promise) { + if (promise->isResolved()) + ctx->setPromise(nullptr); + else + return; + } + if (ctx->coroutineHandle()) { bool done = m_resumeFunction(ctx->coroutineHandle()); @@ -52,7 +55,8 @@ void LLVMExecutableCode::run(ExecutionContext *context) ctx->setFinished(done); } else { - void *handle = m_mainFunction(context->target()); + Target *target = ctx->thread()->target(); + void *handle = m_mainFunction(context, target, target->variableData(), target->listData()); if (!handle) ctx->setFinished(true); @@ -66,6 +70,7 @@ void LLVMExecutableCode::kill(ExecutionContext *context) LLVMExecutionContext *ctx = getContext(context); ctx->setCoroutineHandle(nullptr); ctx->setFinished(true); + ctx->setPromise(nullptr); } void LLVMExecutableCode::reset(ExecutionContext *context) @@ -73,6 +78,7 @@ void LLVMExecutableCode::reset(ExecutionContext *context) LLVMExecutionContext *ctx = getContext(context); ctx->setCoroutineHandle(nullptr); ctx->setFinished(false); + ctx->setPromise(nullptr); } bool LLVMExecutableCode::isFinished(ExecutionContext *context) const @@ -80,29 +86,14 @@ bool LLVMExecutableCode::isFinished(ExecutionContext *context) const return getContext(context)->finished(); } -void LLVMExecutableCode::promise() +std::shared_ptr LLVMExecutableCode::createExecutionContext(Thread *thread) const { -} - -void LLVMExecutableCode::resolvePromise() -{ -} + if (!m_ctx->jitInitialized()) + m_ctx->initJit(); -std::shared_ptr LLVMExecutableCode::createExecutionContext(Target *target) const -{ - return std::make_shared(target); -} - -uint64_t LLVMExecutableCode::lookupFunction(const std::string &name) -{ - auto func = m_jit->get()->lookup(name); - - if (func) - return func->getValue(); - else { - llvm::errs() << "error: failed to lookup LLVM function: " << toString(func.takeError()) << "\n"; - return 0; - } + m_mainFunction = m_ctx->lookupFunction(m_mainFunctionName); + m_resumeFunction = m_ctx->lookupFunction(m_resumeFunctionName); + return std::make_shared(thread); } LLVMExecutionContext *LLVMExecutableCode::getContext(ExecutionContext *context) diff --git a/src/dev/engine/internal/llvmexecutablecode.h b/src/dev/engine/internal/llvm/llvmexecutablecode.h similarity index 58% rename from src/dev/engine/internal/llvmexecutablecode.h rename to src/dev/engine/internal/llvm/llvmexecutablecode.h index 4094fee8..aaa9c9c1 100644 --- a/src/dev/engine/internal/llvmexecutablecode.h +++ b/src/dev/engine/internal/llvm/llvmexecutablecode.h @@ -5,18 +5,18 @@ #include #include #include -#include + +#include "llvmcompilercontext.h" namespace libscratchcpp { -class Target; class LLVMExecutionContext; class LLVMExecutableCode : public ExecutableCode { public: - LLVMExecutableCode(std::unique_ptr module); + LLVMExecutableCode(LLVMCompilerContext *ctx, const std::string &mainFunctionName, const std::string &resumeFunctionName); void run(ExecutionContext *context) override; void kill(libscratchcpp::ExecutionContext *context) override; @@ -24,24 +24,19 @@ class LLVMExecutableCode : public ExecutableCode bool isFinished(ExecutionContext *context) const override; - void promise() override; - void resolvePromise() override; - - std::shared_ptr createExecutionContext(Target *target) const override; + std::shared_ptr createExecutionContext(Thread *thread) const override; private: - uint64_t lookupFunction(const std::string &name); - - using MainFunctionType = void *(*)(Target *); + using MainFunctionType = void *(*)(ExecutionContext *, Target *, ValueData **, List **); using ResumeFunctionType = bool (*)(void *); static LLVMExecutionContext *getContext(ExecutionContext *context); - std::unique_ptr m_ctx; - llvm::Expected> m_jit; - - MainFunctionType m_mainFunction; - ResumeFunctionType m_resumeFunction; + LLVMCompilerContext *m_ctx = nullptr; + std::string m_mainFunctionName; + std::string m_resumeFunctionName; + mutable MainFunctionType m_mainFunction = nullptr; + mutable ResumeFunctionType m_resumeFunction = nullptr; }; } // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvmexecutioncontext.cpp b/src/dev/engine/internal/llvm/llvmexecutioncontext.cpp similarity index 84% rename from src/dev/engine/internal/llvmexecutioncontext.cpp rename to src/dev/engine/internal/llvm/llvmexecutioncontext.cpp index 5934a90a..97696b34 100644 --- a/src/dev/engine/internal/llvmexecutioncontext.cpp +++ b/src/dev/engine/internal/llvm/llvmexecutioncontext.cpp @@ -4,8 +4,8 @@ using namespace libscratchcpp; -LLVMExecutionContext::LLVMExecutionContext(Target *target) : - ExecutionContext(target) +LLVMExecutionContext::LLVMExecutionContext(Thread *thread) : + ExecutionContext(thread) { } diff --git a/src/dev/engine/internal/llvmexecutioncontext.h b/src/dev/engine/internal/llvm/llvmexecutioncontext.h similarity index 91% rename from src/dev/engine/internal/llvmexecutioncontext.h rename to src/dev/engine/internal/llvm/llvmexecutioncontext.h index c6357819..c3c952fc 100644 --- a/src/dev/engine/internal/llvmexecutioncontext.h +++ b/src/dev/engine/internal/llvm/llvmexecutioncontext.h @@ -10,7 +10,7 @@ namespace libscratchcpp class LLVMExecutionContext : public ExecutionContext { public: - LLVMExecutionContext(Target *target); + LLVMExecutionContext(Thread *thread); void *coroutineHandle() const; void setCoroutineHandle(void *newCoroutineHandle); diff --git a/src/dev/engine/internal/llvm/llvmfunctions.cpp b/src/dev/engine/internal/llvm/llvmfunctions.cpp new file mode 100644 index 00000000..eafd9a8c --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmfunctions.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +namespace libscratchcpp +{ + +extern "C" +{ + double llvm_random(ExecutionContext *ctx, ValueData *from, ValueData *to) + { + return value_isInt(from) && value_isInt(to) ? ctx->rng()->randint(value_toLong(from), value_toLong(to)) : ctx->rng()->randintDouble(value_toDouble(from), value_toDouble(to)); + } + + double llvm_random_double(ExecutionContext *ctx, double from, double to) + { + 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); + } +} + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmifstatement.h b/src/dev/engine/internal/llvm/llvmifstatement.h new file mode 100644 index 00000000..83ab9608 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmifstatement.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace llvm +{ + +class Value; +class BasicBlock; + +} // namespace llvm + +namespace libscratchcpp +{ + +struct LLVMIfStatement +{ + llvm::Value *condition = nullptr; + llvm::BasicBlock *beforeIf = nullptr; + llvm::BasicBlock *body = nullptr; + llvm::BasicBlock *elseBranch = nullptr; + llvm::BasicBlock *afterIf = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvminstruction.h b/src/dev/engine/internal/llvm/llvminstruction.h new file mode 100644 index 00000000..8e88e887 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvminstruction.h @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +#include "llvmregister.h" + +namespace libscratchcpp +{ + +class BlockPrototype; +class LLVMLoopScope; + +struct LLVMInstruction +{ + enum class Type + { + FunctionCall, + Add, + Sub, + Mul, + Div, + Random, + RandomInt, + CmpEQ, + CmpGT, + CmpLT, + StrCmpEQCS, // case sensitive + StrCmpEQCI, // case insensitive + And, + Or, + Not, + Mod, + Round, + Abs, + Floor, + Ceil, + Sqrt, + Sin, + Cos, + Tan, + Asin, + Acos, + Atan, + Ln, + Log10, + Exp, + Exp10, + Select, + CreateLocalVariable, + WriteLocalVariable, + ReadLocalVariable, + WriteVariable, + ReadVariable, + ClearList, + RemoveListItem, + AppendToList, + InsertToList, + ListReplace, + GetListContents, + GetListItem, + GetListSize, + GetListItemIndex, + ListContainsItem, + Yield, + BeginIf, + BeginElse, + EndIf, + BeginRepeatLoop, + LoopIndex, + BeginWhileLoop, + BeginRepeatUntilLoop, + BeginLoopCondition, + EndLoop, + Stop, + CallProcedure, + ProcedureArg + }; + + LLVMInstruction(Type type, std::shared_ptr loopScope, bool loopCondition) : + type(type), + loopScope(loopScope), + loopCondition(loopCondition) + { + } + + Type type; + std::string functionName; + std::vector> args; // target type, register + LLVMRegister *functionReturnReg = nullptr; + bool functionTargetArg = false; // whether to add target ptr to function parameters + bool functionCtxArg = false; // whether to add execution context ptr to function parameters + Variable *workVariable = nullptr; // for variables + List *workList = nullptr; // for lists + BlockPrototype *procedurePrototype = nullptr; + size_t procedureArgIndex = 0; + std::shared_ptr loopScope; + bool loopCondition = false; // whether the instruction is part of a loop condition +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmlistptr.h b/src/dev/engine/internal/llvm/llvmlistptr.h new file mode 100644 index 00000000..ca97bc0e --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmlistptr.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace llvm +{ + +class Value; + +} + +namespace libscratchcpp +{ + +class LLVMLoopScope; +class LLVMInstruction; + +struct LLVMListPtr +{ + llvm::Value *ptr = nullptr; + llvm::Value *dataPtr = nullptr; + llvm::Value *sizePtr = nullptr; + llvm::Value *allocatedSizePtr = nullptr; + llvm::Value *dataPtrDirty = nullptr; + Compiler::StaticType type = Compiler::StaticType::Unknown; + + // Used in build phase to check the type safety of lists in loops + std::unordered_map, std::vector>> loopListWrites; // loop scope, write instructions +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmloop.h b/src/dev/engine/internal/llvm/llvmloop.h new file mode 100644 index 00000000..3747b39b --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmloop.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace llvm +{ + +class Value; +class BasicBlock; + +} // namespace llvm + +namespace libscratchcpp +{ + +struct LLVMLoop +{ + bool isRepeatLoop = false; + llvm::Value *index = nullptr; + llvm::BasicBlock *conditionBranch = nullptr; + llvm::BasicBlock *afterLoop = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmloopscope.h b/src/dev/engine/internal/llvm/llvmloopscope.h new file mode 100644 index 00000000..859c2cb1 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmloopscope.h @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace libscratchcpp +{ + +struct LLVMLoopScope +{ + bool containsYield = false; + std::shared_ptr parentScope; + std::vector> childScopes; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmprocedure.h b/src/dev/engine/internal/llvm/llvmprocedure.h new file mode 100644 index 00000000..7cb57679 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmprocedure.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace libscratchcpp +{ + +struct LLVMProcedure +{ + // TODO: Implement procedures + bool warp = false; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmregister.h b/src/dev/engine/internal/llvm/llvmregister.h new file mode 100644 index 00000000..2c7bc7f5 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmregister.h @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "llvmregisterbase.h" + +namespace llvm +{ + +class Value; + +} + +namespace libscratchcpp +{ + +struct LLVMRegister + : public LLVMRegisterBase + , public CompilerValue +{ + LLVMRegister(Compiler::StaticType type) : + LLVMRegisterBase(), + CompilerValue(type) + { + } + + const Value &constValue() const override + { + if (isConst()) + return static_cast(static_cast(this))->value(); + else { + static const Value null = Value(); + return null; + } + } +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmregisterbase.h b/src/dev/engine/internal/llvm/llvmregisterbase.h new file mode 100644 index 00000000..33aace03 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmregisterbase.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace llvm +{ + +class Value; + +} + +namespace libscratchcpp +{ + +class LLVMInstruction; + +struct LLVMRegisterBase +{ + virtual const Value &constValue() const = 0; + + llvm::Value *value = nullptr; + bool isRawValue = false; + std::shared_ptr instruction; +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmtypes.cpp b/src/dev/engine/internal/llvm/llvmtypes.cpp new file mode 100644 index 00000000..5dfc6075 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmtypes.cpp @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "llvmtypes.h" + +using namespace libscratchcpp; + +llvm::StructType *LLVMTypes::createValueDataType(llvm::IRBuilder<> *builder) +{ + llvm::LLVMContext &ctx = builder->getContext(); + + // Create the ValueData struct + llvm::Type *unionType = builder->getInt64Ty(); // 64 bits is the largest size + + llvm::Type *valueType = llvm::Type::getInt32Ty(ctx); // Assuming ValueType is a 32-bit enum + llvm::Type *padding = llvm::Type::getInt32Ty(ctx); // Padding for alignment + llvm::Type *sizeType = llvm::Type::getInt64Ty(ctx); // size_t + + llvm::StructType *ret = llvm::StructType::create(ctx, "ValueData"); + ret->setBody({ unionType, valueType, padding, sizeType }); + + return ret; +} diff --git a/src/dev/engine/internal/llvm/llvmtypes.h b/src/dev/engine/internal/llvm/llvmtypes.h new file mode 100644 index 00000000..ae0e3c85 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmtypes.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace llvm +{ + +class StructValue; + +} + +namespace libscratchcpp +{ + +class LLVMTypes +{ + public: + static llvm::StructType *createValueDataType(llvm::IRBuilder<> *builder); +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvm/llvmvariableptr.h b/src/dev/engine/internal/llvm/llvmvariableptr.h new file mode 100644 index 00000000..28105f19 --- /dev/null +++ b/src/dev/engine/internal/llvm/llvmvariableptr.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace llvm +{ + +class Value; + +} + +namespace libscratchcpp +{ + +class LLVMLoopScope; +class LLVMInstruction; + +struct LLVMVariablePtr +{ + llvm::Value *stackPtr = nullptr; + llvm::Value *heapPtr = nullptr; + Compiler::StaticType type = Compiler::StaticType::Unknown; + bool onStack = false; + bool changed = false; + + // Used in build phase to check the type safety of variables in loops + std::unordered_map, std::vector>> loopVariableWrites; // loop scope, write instructions +}; + +} // namespace libscratchcpp diff --git a/src/dev/engine/internal/llvmcodebuilder.cpp b/src/dev/engine/internal/llvmcodebuilder.cpp deleted file mode 100644 index 22f3d552..00000000 --- a/src/dev/engine/internal/llvmcodebuilder.cpp +++ /dev/null @@ -1,1267 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include -#include -#include - -#include "llvmcodebuilder.h" -#include "llvmexecutablecode.h" - -using namespace libscratchcpp; - -static std::unordered_map - TYPE_MAP = { { ValueType::Number, Compiler::StaticType::Number }, { ValueType::Bool, Compiler::StaticType::Bool }, { ValueType::String, Compiler::StaticType::String } }; - -LLVMCodeBuilder::LLVMCodeBuilder(const std::string &id, bool warp) : - m_id(id), - m_module(std::make_unique(id, m_ctx)), - m_builder(m_ctx), - m_defaultWarp(warp), - m_warp(warp) -{ - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); - - m_constValues.push_back({}); - m_regs.push_back({}); - initTypes(); -} - -std::shared_ptr LLVMCodeBuilder::finalize() -{ - // Do not create coroutine if there are no yield instructions - if (!m_warp) { - auto it = std::find_if(m_steps.begin(), m_steps.end(), [](const Step &step) { return step.type == Step::Type::Yield; }); - - if (it == m_steps.end()) - m_warp = true; - } - - // Create function - // void *f(Target *) - llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); - llvm::FunctionType *funcType = llvm::FunctionType::get(pointerType, pointerType, false); - llvm::Function *func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "f", m_module.get()); - - llvm::BasicBlock *entry = llvm::BasicBlock::Create(m_ctx, "entry", func); - m_builder.SetInsertPoint(entry); - - // Init coroutine - Coroutine coro; - - if (!m_warp) - coro = initCoroutine(func); - - std::vector ifStatements; - std::vector loops; - m_heap.clear(); - - // Execute recorded steps - for (const Step &step : m_steps) { - switch (step.type) { - case Step::Type::FunctionCall: { - std::vector types; - std::vector args; - - // Add target pointer arg - assert(func->arg_size() == 1); - types.push_back(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0)); - args.push_back(func->getArg(0)); - - // Args - for (auto &arg : step.args) { - types.push_back(getType(arg.first)); - args.push_back(castValue(arg.second, arg.first)); - } - - llvm::Value *ret = m_builder.CreateCall(resolveFunction(step.functionName, llvm::FunctionType::get(getType(step.functionReturnType), types, false)), args); - - if (step.functionReturnReg) { - step.functionReturnReg->value = ret; - - if (step.functionReturnType == Compiler::StaticType::String) - m_heap.push_back(step.functionReturnReg->value); - } - - break; - } - - case Step::Type::Add: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0]; - const auto &arg2 = step.args[1]; - llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); - llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); - step.functionReturnReg->value = m_builder.CreateFAdd(num1, num2); - break; - } - - case Step::Type::Sub: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0]; - const auto &arg2 = step.args[1]; - llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); - llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); - step.functionReturnReg->value = m_builder.CreateFSub(num1, num2); - break; - } - - case Step::Type::Mul: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0]; - const auto &arg2 = step.args[1]; - llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); - llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); - step.functionReturnReg->value = m_builder.CreateFMul(num1, num2); - break; - } - - case Step::Type::Div: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0]; - const auto &arg2 = step.args[1]; - llvm::Value *num1 = removeNaN(castValue(arg1.second, arg1.first)); - llvm::Value *num2 = removeNaN(castValue(arg2.second, arg2.first)); - step.functionReturnReg->value = m_builder.CreateFDiv(num1, num2); - break; - } - - case Step::Type::CmpEQ: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0].second; - const auto &arg2 = step.args[1].second; - step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::EQ); - break; - } - - case Step::Type::CmpGT: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0].second; - const auto &arg2 = step.args[1].second; - step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::GT); - break; - } - - case Step::Type::CmpLT: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0].second; - const auto &arg2 = step.args[1].second; - step.functionReturnReg->value = createComparison(arg1, arg2, Comparison::LT); - break; - } - - case Step::Type::And: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0]; - const auto &arg2 = step.args[1]; - llvm::Value *bool1 = castValue(arg1.second, arg1.first); - llvm::Value *bool2 = castValue(arg2.second, arg2.first); - step.functionReturnReg->value = m_builder.CreateAnd(bool1, bool2); - break; - } - - case Step::Type::Or: { - assert(step.args.size() == 2); - const auto &arg1 = step.args[0]; - const auto &arg2 = step.args[1]; - llvm::Value *bool1 = castValue(arg1.second, arg1.first); - llvm::Value *bool2 = castValue(arg2.second, arg2.first); - step.functionReturnReg->value = m_builder.CreateOr(bool1, bool2); - break; - } - - case Step::Type::Not: { - assert(step.args.size() == 1); - const auto &arg = step.args[0]; - llvm::Value *value = castValue(arg.second, arg.first); - step.functionReturnReg->value = m_builder.CreateNot(value); - break; - } - - case Step::Type::Yield: - if (!m_warp) { - freeHeap(); - m_builder.CreateStore(m_builder.getInt1(true), coro.didSuspend); - llvm::BasicBlock *resumeBranch = llvm::BasicBlock::Create(m_ctx, "", func); - llvm::Value *noneToken = llvm::ConstantTokenNone::get(m_ctx); - llvm::Value *suspendResult = m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_suspend), { noneToken, m_builder.getInt1(false) }); - llvm::SwitchInst *sw = m_builder.CreateSwitch(suspendResult, coro.suspend, 2); - sw->addCase(m_builder.getInt8(0), resumeBranch); - sw->addCase(m_builder.getInt8(1), coro.cleanup); - m_builder.SetInsertPoint(resumeBranch); - } - - break; - - case Step::Type::BeginIf: { - IfStatement statement; - statement.beforeIf = m_builder.GetInsertBlock(); - statement.body = llvm::BasicBlock::Create(m_ctx, "", func); - - // Use last reg - 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 - 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, "", func); - freeHeap(); - m_builder.CreateBr(statement.afterIf); - - // Create else branch - assert(!statement.elseBranch); - statement.elseBranch = llvm::BasicBlock::Create(m_ctx, "", func); - - // 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, "", func); - - freeHeap(); - 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, "", func); - loop.conditionBranch = llvm::BasicBlock::Create(m_ctx, "", func); - loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); - - // Use last reg for count - assert(step.args.size() == 1); - const auto ® = step.args[0]; - assert(reg.first == Compiler::StaticType::Number); - llvm::Value *count = castValue(reg.second, reg.first); - - // 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) - 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, "", func); - - if (!loop.afterLoop) - loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); - - 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, "", func); - loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); - - // Use last reg - assert(step.args.size() == 1); - 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 - 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, "", func); - loop.afterLoop = llvm::BasicBlock::Create(m_ctx, "", func); - - // Use last reg - assert(step.args.size() == 1); - 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 - m_builder.SetInsertPoint(body); - break; - } - - case Step::Type::BeginLoopCondition: { - Loop 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); - 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 - freeHeap(); - m_builder.CreateBr(loop.conditionBranch); - - // Switch to the branch after the loop - m_builder.SetInsertPoint(loop.afterLoop); - - loops.pop_back(); - break; - } - } - } - - freeHeap(); - - // Add final suspend point - if (!m_warp) { - llvm::BasicBlock *endBranch = llvm::BasicBlock::Create(m_ctx, "end", func); - llvm::BasicBlock *finalSuspendBranch = llvm::BasicBlock::Create(m_ctx, "finalSuspend", func); - m_builder.CreateCondBr(m_builder.CreateLoad(m_builder.getInt1Ty(), coro.didSuspend), finalSuspendBranch, endBranch); - - m_builder.SetInsertPoint(finalSuspendBranch); - llvm::Value *suspendResult = - m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_suspend), { llvm::ConstantTokenNone::get(m_ctx), m_builder.getInt1(true) }); - llvm::SwitchInst *sw = m_builder.CreateSwitch(suspendResult, coro.suspend, 2); - sw->addCase(m_builder.getInt8(0), endBranch); // unreachable - sw->addCase(m_builder.getInt8(1), coro.cleanup); - - m_builder.SetInsertPoint(endBranch); - } - - // End and verify the function - if (!m_tmpRegs.empty()) { - std::cout - << "warning: " << m_tmpRegs.size() << " registers were leaked by script '" << m_module->getName().str() << "', function '" << func->getName().str() - << "' (if you see this as a regular user, this is a bug and should be reported)" << std::endl; - } - - if (m_warp) - m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType)); - else - m_builder.CreateBr(coro.freeMemRet); - - verifyFunction(func); - - // Create resume function - // bool resume(void *) - funcType = llvm::FunctionType::get(m_builder.getInt1Ty(), pointerType, false); - func = llvm::Function::Create(funcType, llvm::Function::ExternalLinkage, "resume", m_module.get()); - - entry = llvm::BasicBlock::Create(m_ctx, "entry", func); - m_builder.SetInsertPoint(entry); - - if (m_warp) - m_builder.CreateRet(m_builder.getInt1(true)); - else { - llvm::Value *coroHandle = func->getArg(0); - m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_resume), { coroHandle }); - llvm::Value *done = m_builder.CreateCall(llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_done), { coroHandle }); - m_builder.CreateRet(done); - } - - verifyFunction(func); - -#ifdef PRINT_LLVM_IR - std::cout << std::endl << "=== LLVM IR (" << m_module->getName().str() << ") ===" << std::endl; - m_module->print(llvm::outs(), nullptr); - std::cout << "==============" << std::endl << std::endl; -#endif - - // Optimize - optimize(); - - return std::make_shared(std::move(m_module)); -} - -void LLVMCodeBuilder::addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector &argTypes) -{ - Step step(Step::Type::FunctionCall); - step.functionName = functionName; - - assert(m_tmpRegs.size() >= argTypes.size()); - size_t j = 0; - - for (size_t i = m_tmpRegs.size() - argTypes.size(); i < m_tmpRegs.size(); i++) - step.args.push_back({ argTypes[j++], m_tmpRegs[i] }); - - m_tmpRegs.erase(m_tmpRegs.end() - argTypes.size(), m_tmpRegs.end()); - - step.functionReturnType = returnType; - - if (returnType != Compiler::StaticType::Void) { - auto reg = std::make_shared(returnType); - reg->isRawValue = true; - step.functionReturnReg = reg; - m_regs[m_currentFunction].push_back(reg); - m_tmpRegs.push_back(reg); - } - - m_steps.push_back(step); -} - -void LLVMCodeBuilder::addConstValue(const Value &value) -{ - auto reg = std::make_shared(TYPE_MAP[value.type()]); - reg->isConstValue = true; - reg->constValue = value; - m_regs[m_currentFunction].push_back(reg); - m_tmpRegs.push_back(reg); -} - -void LLVMCodeBuilder::addVariableValue(Variable *variable) -{ -} - -void LLVMCodeBuilder::addListContents(List *list) -{ -} - -void LLVMCodeBuilder::createAdd() -{ - createOp(Step::Type::Add, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); -} - -void LLVMCodeBuilder::createSub() -{ - createOp(Step::Type::Sub, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); -} - -void LLVMCodeBuilder::createMul() -{ - createOp(Step::Type::Mul, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); -} - -void LLVMCodeBuilder::createDiv() -{ - createOp(Step::Type::Div, Compiler::StaticType::Number, Compiler::StaticType::Number, 2); -} - -void LLVMCodeBuilder::createCmpEQ() -{ - createOp(Step::Type::CmpEQ, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2); -} - -void LLVMCodeBuilder::createCmpGT() -{ - createOp(Step::Type::CmpGT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2); -} - -void LLVMCodeBuilder::createCmpLT() -{ - createOp(Step::Type::CmpLT, Compiler::StaticType::Bool, Compiler::StaticType::Number, 2); -} - -void LLVMCodeBuilder::createAnd() -{ - createOp(Step::Type::And, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2); -} - -void LLVMCodeBuilder::createOr() -{ - createOp(Step::Type::Or, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 2); -} - -void LLVMCodeBuilder::createNot() -{ - createOp(Step::Type::Not, Compiler::StaticType::Bool, Compiler::StaticType::Bool, 1); -} - -void LLVMCodeBuilder::beginIfStatement() -{ - Step step(Step::Type::BeginIf); - assert(!m_tmpRegs.empty()); - step.args.push_back({ Compiler::StaticType::Bool, 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({ Compiler::StaticType::Number, 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({ Compiler::StaticType::Bool, m_tmpRegs.back() }); - m_tmpRegs.pop_back(); - m_steps.push_back(step); -} - -void LLVMCodeBuilder::beginRepeatUntilLoop() -{ - Step step(Step::Type::BeginRepeatUntilLoop); - assert(!m_tmpRegs.empty()); - step.args.push_back({ Compiler::StaticType::Bool, 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() -{ - if (!m_warp) - m_steps.push_back(Step(Step::Type::Yield)); - - m_steps.push_back(Step(Step::Type::EndLoop)); -} - -void LLVMCodeBuilder::yield() -{ - m_steps.push_back({ Step::Type::Yield }); - m_currentFunction++; - - assert(m_currentFunction == m_constValues.size()); - m_constValues.push_back({}); - - assert(m_currentFunction == m_regs.size()); - m_regs.push_back({}); -} - -void LLVMCodeBuilder::initTypes() -{ - // Create the ValueData struct - llvm::Type *unionType = m_builder.getInt64Ty(); // 64 bits is the largest size - - llvm::Type *valueType = llvm::Type::getInt32Ty(m_ctx); // Assuming ValueType is a 32-bit enum - llvm::Type *sizeType = llvm::Type::getInt64Ty(m_ctx); // size_t - - m_valueDataType = llvm::StructType::create(m_ctx, "ValueData"); - m_valueDataType->setBody({ unionType, valueType, sizeType }); -} - -LLVMCodeBuilder::Coroutine LLVMCodeBuilder::initCoroutine(llvm::Function *func) -{ - // Set presplitcoroutine attribute - func->setPresplitCoroutine(); - - // Coroutine intrinsics - llvm::Function *coroId = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_id); - llvm::Function *coroSize = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_size, m_builder.getInt64Ty()); - llvm::Function *coroBegin = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_begin); - llvm::Function *coroEnd = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_end); - llvm::Function *coroFree = llvm::Intrinsic::getDeclaration(m_module.get(), llvm::Intrinsic::coro_free); - - // Init coroutine - Coroutine coro; - llvm::PointerType *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); - llvm::Constant *nullPointer = llvm::ConstantPointerNull::get(pointerType); - llvm::Value *coroIdRet = m_builder.CreateCall(coroId, { m_builder.getInt32(8), nullPointer, nullPointer, nullPointer }); - - // Allocate memory - llvm::Value *coroSizeRet = m_builder.CreateCall(coroSize, std::nullopt, "size"); - llvm::Function *mallocFunc = llvm::Function::Create(llvm::FunctionType::get(pointerType, { m_builder.getInt64Ty() }, false), llvm::Function::ExternalLinkage, "malloc", m_module.get()); - llvm::Value *alloc = m_builder.CreateCall(mallocFunc, coroSizeRet, "mem"); - - // Begin - coro.handle = m_builder.CreateCall(coroBegin, { coroIdRet, alloc }); - coro.didSuspend = m_builder.CreateAlloca(m_builder.getInt1Ty(), nullptr, "didSuspend"); - m_builder.CreateStore(m_builder.getInt1(false), coro.didSuspend); - llvm::BasicBlock *entry = m_builder.GetInsertBlock(); - - // Create suspend branch - coro.suspend = llvm::BasicBlock::Create(m_ctx, "suspend", func); - m_builder.SetInsertPoint(coro.suspend); - m_builder.CreateCall(coroEnd, { coro.handle, m_builder.getInt1(false), llvm::ConstantTokenNone::get(m_ctx) }); - m_builder.CreateRet(coro.handle); - - // Create free branches - coro.freeMemRet = llvm::BasicBlock::Create(m_ctx, "freeMemRet", func); - m_builder.SetInsertPoint(coro.freeMemRet); - m_builder.CreateFree(alloc); - m_builder.CreateRet(llvm::ConstantPointerNull::get(pointerType)); - - llvm::BasicBlock *freeBranch = llvm::BasicBlock::Create(m_ctx, "free", func); - m_builder.SetInsertPoint(freeBranch); - m_builder.CreateFree(alloc); - m_builder.CreateBr(coro.suspend); - - // Create cleanup branch - coro.cleanup = llvm::BasicBlock::Create(m_ctx, "cleanup", func); - m_builder.SetInsertPoint(coro.cleanup); - llvm::Value *mem = m_builder.CreateCall(coroFree, { coroIdRet, coro.handle }); - llvm::Value *needFree = m_builder.CreateIsNotNull(mem); - m_builder.CreateCondBr(needFree, freeBranch, coro.suspend); - - m_builder.SetInsertPoint(entry); - return coro; -} - -void LLVMCodeBuilder::verifyFunction(llvm::Function *func) -{ - if (llvm::verifyFunction(*func, &llvm::errs())) { - llvm::errs() << "error: LLVM function verficiation failed!\n"; - llvm::errs() << "script hat ID: " << m_id << "\n"; - } -} - -void LLVMCodeBuilder::optimize() -{ - llvm::PassBuilder passBuilder; - llvm::LoopAnalysisManager loopAnalysisManager; - llvm::FunctionAnalysisManager functionAnalysisManager; - llvm::CGSCCAnalysisManager cGSCCAnalysisManager; - llvm::ModuleAnalysisManager moduleAnalysisManager; - - passBuilder.registerModuleAnalyses(moduleAnalysisManager); - passBuilder.registerCGSCCAnalyses(cGSCCAnalysisManager); - passBuilder.registerFunctionAnalyses(functionAnalysisManager); - passBuilder.registerLoopAnalyses(loopAnalysisManager); - passBuilder.crossRegisterProxies(loopAnalysisManager, functionAnalysisManager, cGSCCAnalysisManager, moduleAnalysisManager); - - llvm::ModulePassManager modulePassManager = passBuilder.buildPerModuleDefaultPipeline(llvm::OptimizationLevel::O3); - modulePassManager.run(*m_module, moduleAnalysisManager); -} - -void LLVMCodeBuilder::freeHeap() -{ - // Free dynamically allocated memory - for (llvm::Value *ptr : m_heap) - m_builder.CreateFree(ptr); - - m_heap.clear(); -} - -llvm::Value *LLVMCodeBuilder::castValue(std::shared_ptr reg, Compiler::StaticType targetType) -{ - if (reg->isConstValue) - return castConstValue(reg->constValue, targetType); - - if (reg->isRawValue) - return castRawValue(reg, targetType); - - assert(reg->type != Compiler::StaticType::Void && targetType != Compiler::StaticType::Void); - - switch (targetType) { - case Compiler::StaticType::Number: - switch (reg->type) { - case Compiler::StaticType::Number: { - // Read number directly - llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); - return m_builder.CreateLoad(m_builder.getDoubleTy(), ptr); - } - - case Compiler::StaticType::Bool: { - // Read boolean and cast to double - llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); - llvm::Value *boolValue = m_builder.CreateLoad(m_builder.getInt1Ty(), ptr); - return m_builder.CreateSIToFP(boolValue, m_builder.getDoubleTy()); - } - - case Compiler::StaticType::String: { - // Convert string to double - return m_builder.CreateCall(resolve_value_toDouble(), reg->value); - } - - default: - assert(false); - return nullptr; - } - - case Compiler::StaticType::Bool: - switch (reg->type) { - case Compiler::StaticType::Number: { - // True if != 0 - llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); - llvm::Value *numberValue = m_builder.CreateLoad(m_builder.getDoubleTy(), ptr); - return m_builder.CreateFCmpONE(numberValue, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0))); - } - - case Compiler::StaticType::Bool: { - // Read boolean directly - llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); - return m_builder.CreateLoad(m_builder.getInt1Ty(), ptr); - } - - case Compiler::StaticType::String: - // Convert string to bool - return m_builder.CreateCall(resolve_value_toBool(), reg->value); - - default: - assert(false); - return nullptr; - } - - case Compiler::StaticType::String: - switch (reg->type) { - case Compiler::StaticType::Number: - case Compiler::StaticType::Bool: { - // Cast to string - llvm::Value *ptr = m_builder.CreateCall(resolve_value_toCString(), reg->value); - m_heap.push_back(ptr); // deallocate later - return ptr; - } - - case Compiler::StaticType::String: { - // Read string pointer directly - llvm::Value *ptr = m_builder.CreateStructGEP(m_valueDataType, reg->value, 0); - return m_builder.CreateLoad(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), ptr); - } - - default: - assert(false); - return nullptr; - } - - default: - assert(false); - return nullptr; - } -} - -llvm::Value *LLVMCodeBuilder::castRawValue(std::shared_ptr reg, Compiler::StaticType targetType) -{ - if (reg->type == targetType) - return reg->value; - - switch (targetType) { - case Compiler::StaticType::Number: - switch (reg->type) { - case Compiler::StaticType::Bool: - // Cast bool to double - return m_builder.CreateUIToFP(reg->value, m_builder.getDoubleTy()); - - case Compiler::StaticType::String: { - // Convert string to double - return m_builder.CreateCall(resolve_value_stringToDouble(), reg->value); - } - - default: - assert(false); - return nullptr; - } - - case Compiler::StaticType::Bool: - switch (reg->type) { - case Compiler::StaticType::Number: - // Cast double to bool (true if != 0) - return m_builder.CreateFCmpONE(reg->value, llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0))); - - case Compiler::StaticType::String: - // Convert string to bool - return m_builder.CreateCall(resolve_value_stringToBool(), reg->value); - - default: - assert(false); - return nullptr; - } - - case Compiler::StaticType::String: - switch (reg->type) { - 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 - return ptr; - } - - case Compiler::StaticType::Bool: { - // Convert bool to string - llvm::Value *ptr = m_builder.CreateCall(resolve_value_boolToCString(), reg->value); - // NOTE: Dot not deallocate later - return ptr; - } - - default: - assert(false); - return nullptr; - } - - default: - assert(false); - return nullptr; - } -} - -llvm::Constant *LLVMCodeBuilder::castConstValue(const Value &value, Compiler::StaticType targetType) -{ - switch (targetType) { - case Compiler::StaticType::Number: { - const double nan = std::numeric_limits::quiet_NaN(); - return llvm::ConstantFP::get(m_ctx, llvm::APFloat(value.isNaN() ? nan : value.toDouble())); - } - - case Compiler::StaticType::Bool: - return m_builder.getInt1(value.toBool()); - - case Compiler::StaticType::String: - return m_builder.CreateGlobalStringPtr(value.toString()); - - default: - assert(false); - return nullptr; - } -} - -llvm::Type *LLVMCodeBuilder::getType(Compiler::StaticType type) -{ - switch (type) { - case Compiler::StaticType::Void: - return m_builder.getVoidTy(); - - case Compiler::StaticType::Number: - return m_builder.getDoubleTy(); - - case Compiler::StaticType::Bool: - return m_builder.getInt1Ty(); - - case Compiler::StaticType::String: - return llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); - - default: - assert(false); - return nullptr; - } -} - -llvm::Value *LLVMCodeBuilder::isNaN(llvm::Value *num) -{ - return m_builder.CreateFCmpUNO(num, num); -} - -llvm::Value *LLVMCodeBuilder::removeNaN(llvm::Value *num) -{ - // Replace NaN with zero - return m_builder.CreateSelect(isNaN(num), llvm::ConstantFP::get(m_ctx, llvm::APFloat(0.0)), num); -} - -void LLVMCodeBuilder::createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount) -{ - Step step(type); - - assert(m_tmpRegs.size() >= argCount); - size_t j = 0; - - for (size_t i = m_tmpRegs.size() - argCount; i < m_tmpRegs.size(); i++) - step.args.push_back({ argType, m_tmpRegs[i] }); - - m_tmpRegs.erase(m_tmpRegs.end() - argCount, m_tmpRegs.end()); - - auto ret = std::make_shared(retType); - ret->isRawValue = true; - step.functionReturnReg = ret; - m_regs[m_currentFunction].push_back(ret); - m_tmpRegs.push_back(ret); - - m_steps.push_back(step); -} - -llvm::Value *LLVMCodeBuilder::createValue(std::shared_ptr reg) -{ - if (reg->isConstValue) { - // Create a constant ValueData instance and store it - llvm::Constant *value = castConstValue(reg->constValue, TYPE_MAP[reg->constValue.type()]); - llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType); - - if (reg->constValue.type() == ValueType::String) - value = llvm::ConstantExpr::getPtrToInt(value, m_valueDataType->getElementType(0)); - else - value = llvm::ConstantExpr::getBitCast(value, m_valueDataType->getElementType(0)); - - llvm::Constant *type = m_builder.getInt32(static_cast(reg->constValue.type())); - llvm::Constant *constValue = llvm::ConstantStruct::get(m_valueDataType, { value, type, m_builder.getInt64(0) }); - m_builder.CreateStore(constValue, ret); - - return ret; - } else if (reg->isRawValue) { - llvm::Value *value = castRawValue(reg, reg->type); - llvm::Value *ret = m_builder.CreateAlloca(m_valueDataType); - - // Store value - llvm::Value *valueField = m_builder.CreateStructGEP(m_valueDataType, ret, 0); - m_builder.CreateStore(value, valueField); - - auto it = std::find_if(TYPE_MAP.begin(), TYPE_MAP.end(), [®](const std::pair &pair) { return pair.second == reg->type; }); - - if (it == TYPE_MAP.end()) { - assert(false); - return nullptr; - } - - // Store type - llvm::Value *typeField = m_builder.CreateStructGEP(m_valueDataType, ret, 1); - ValueType type = it->first; - m_builder.CreateStore(m_builder.getInt32(static_cast(type)), typeField); - - return ret; - } else - return reg->value; -} - -llvm::Value *LLVMCodeBuilder::createComparison(std::shared_ptr arg1, std::shared_ptr arg2, Comparison type) -{ - auto type1 = arg1->type; - auto type2 = arg2->type; - - if (arg1->isConstValue && arg2->isConstValue) { - // If both operands are constant, perform the comparison at compile time - bool result = false; - - switch (type) { - case Comparison::EQ: - result = arg1->constValue == arg2->constValue; - break; - - case Comparison::GT: - result = arg1->constValue > arg2->constValue; - break; - - case Comparison::LT: - result = arg1->constValue < arg2->constValue; - break; - - default: - assert(false); - return nullptr; - } - - return m_builder.getInt1(result); - } else { - // Optimize comparison of constant with number/bool - if (arg1->isConstValue && arg1->constValue.isValidNumber() && (type2 == Compiler::StaticType::Number || type2 == Compiler::StaticType::Bool)) - type1 = Compiler::StaticType::Number; - - if (arg2->isConstValue && arg2->constValue.isValidNumber() && (type1 == Compiler::StaticType::Number || type1 == Compiler::StaticType::Bool)) - type2 = Compiler::StaticType::Number; - - // Optimize number and bool comparison - int optNumberBool = 0; - - if (type1 == Compiler::StaticType::Number && type2 == Compiler::StaticType::Bool) { - type2 = Compiler::StaticType::Number; - optNumberBool = 2; // operand 2 was bool - } - - if (type1 == Compiler::StaticType::Bool && type2 == Compiler::StaticType::Number) { - type1 = Compiler::StaticType::Number; - optNumberBool = 1; // operand 1 was bool - } - - if (type1 != type2 || type1 == Compiler::StaticType::Unknown || type2 == Compiler::StaticType::Unknown) { - // If the types are different or at least one of them - // is unknown, we must use value functions - llvm::Value *value1 = createValue(arg1); - llvm::Value *value2 = createValue(arg2); - - switch (type) { - case Comparison::EQ: - return m_builder.CreateCall(resolve_value_equals(), { value1, value2 }); - - case Comparison::GT: - return m_builder.CreateCall(resolve_value_greater(), { value1, value2 }); - - case Comparison::LT: - return m_builder.CreateCall(resolve_value_lower(), { value1, value2 }); - - default: - assert(false); - return nullptr; - } - } else { - // Compare raw values - llvm::Value *value1 = castValue(arg1, type1); - llvm::Value *value2 = castValue(arg2, type2); - assert(type1 == type2); - - switch (type1) { - case Compiler::StaticType::Number: { - // Compare two numbers - switch (type) { - case Comparison::EQ: { - llvm::Value *nan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN - llvm::Value *cmp = m_builder.CreateFCmpOEQ(value1, value2); - return m_builder.CreateSelect(nan, m_builder.getInt1(true), cmp); - } - - case Comparison::GT: { - llvm::Value *bothNan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN - llvm::Value *cmp = m_builder.CreateFCmpOGT(value1, value2); - llvm::Value *nan; - llvm::Value *nanCmp; - - if (optNumberBool == 1) { - nan = isNaN(value2); - nanCmp = castValue(arg1, Compiler::StaticType::Bool); - } else if (optNumberBool == 2) { - nan = isNaN(value1); - nanCmp = m_builder.CreateNot(castValue(arg2, Compiler::StaticType::Bool)); - } else { - nan = isNaN(value1); - nanCmp = m_builder.CreateFCmpUGT(value1, value2); - } - - return m_builder.CreateAnd(m_builder.CreateNot(bothNan), m_builder.CreateSelect(nan, nanCmp, cmp)); - } - - case Comparison::LT: { - llvm::Value *bothNan = m_builder.CreateAnd(isNaN(value1), isNaN(value2)); // NaN == NaN - llvm::Value *cmp = m_builder.CreateFCmpOLT(value1, value2); - llvm::Value *nan; - llvm::Value *nanCmp; - - if (optNumberBool == 1) { - nan = isNaN(value2); - nanCmp = m_builder.CreateNot(castValue(arg1, Compiler::StaticType::Bool)); - } else if (optNumberBool == 2) { - nan = isNaN(value1); - nanCmp = castValue(arg2, Compiler::StaticType::Bool); - } else { - nan = isNaN(value2); - nanCmp = m_builder.CreateFCmpULT(value1, value2); - } - - return m_builder.CreateAnd(m_builder.CreateNot(bothNan), m_builder.CreateSelect(nan, nanCmp, cmp)); - } - - default: - assert(false); - return nullptr; - } - } - - case Compiler::StaticType::Bool: - // Compare two booleans - switch (type) { - case Comparison::EQ: - return m_builder.CreateICmpEQ(value1, value2); - - case Comparison::GT: - // value1 && !value2 - return m_builder.CreateAnd(value1, m_builder.CreateNot(value2)); - - case Comparison::LT: - // value2 && !value1 - return m_builder.CreateAnd(value2, m_builder.CreateNot(value1)); - - default: - assert(false); - return nullptr; - } - - case Compiler::StaticType::String: { - // Compare two strings - llvm::Value *cmpRet = m_builder.CreateCall(resolve_strcasecmp(), { value1, value2 }); - - switch (type) { - case Comparison::EQ: - return m_builder.CreateICmpEQ(cmpRet, m_builder.getInt32(0)); - - case Comparison::GT: - return m_builder.CreateICmpSGT(cmpRet, m_builder.getInt32(0)); - - case Comparison::LT: - return m_builder.CreateICmpSLT(cmpRet, m_builder.getInt32(0)); - - default: - assert(false); - return nullptr; - } - } - - default: - assert(false); - return nullptr; - } - } - } -} - -llvm::FunctionCallee LLVMCodeBuilder::resolveFunction(const std::string name, llvm::FunctionType *type) -{ - return m_module->getOrInsertFunction(name, type); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_init() -{ - return resolveFunction("value_init", llvm::FunctionType::get(m_builder.getVoidTy(), m_valueDataType->getPointerTo(), false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_free() -{ - return resolveFunction("value_free", llvm::FunctionType::get(m_builder.getVoidTy(), m_valueDataType->getPointerTo(), false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_long() -{ - return resolveFunction("value_assign_long", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt64Ty() }, false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_double() -{ - return resolveFunction("value_assign_double", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getDoubleTy() }, false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_bool() -{ - return resolveFunction("value_assign_double", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), m_builder.getInt1Ty() }, false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_assign_cstring() -{ - return resolveFunction("value_assign_cstring", llvm::FunctionType::get(m_builder.getVoidTy(), { m_valueDataType->getPointerTo(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0) }, false)); -} - -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)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_toCString() -{ - return resolveFunction("value_toCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_valueDataType->getPointerTo(), false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_doubleToCString() -{ - return resolveFunction("value_doubleToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_builder.getDoubleTy(), false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_boolToCString() -{ - return resolveFunction("value_boolToCString", llvm::FunctionType::get(llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), m_builder.getInt1Ty(), false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToDouble() -{ - return resolveFunction("value_stringToDouble", llvm::FunctionType::get(m_builder.getDoubleTy(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_stringToBool() -{ - return resolveFunction("value_stringToBool", llvm::FunctionType::get(m_builder.getInt1Ty(), llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0), false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_equals() -{ - llvm::Type *valuePtr = m_valueDataType->getPointerTo(); - return resolveFunction("value_equals", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_greater() -{ - llvm::Type *valuePtr = m_valueDataType->getPointerTo(); - return resolveFunction("value_greater", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_value_lower() -{ - llvm::Type *valuePtr = m_valueDataType->getPointerTo(); - return resolveFunction("value_lower", llvm::FunctionType::get(m_builder.getInt1Ty(), { valuePtr, valuePtr }, false)); -} - -llvm::FunctionCallee LLVMCodeBuilder::resolve_strcasecmp() -{ - llvm::Type *pointerType = llvm::PointerType::get(llvm::Type::getInt8Ty(m_ctx), 0); - return resolveFunction("strcasecmp", llvm::FunctionType::get(m_builder.getInt32Ty(), { pointerType, pointerType }, false)); -} diff --git a/src/dev/engine/internal/llvmcodebuilder.h b/src/dev/engine/internal/llvmcodebuilder.h deleted file mode 100644 index 5fb17df5..00000000 --- a/src/dev/engine/internal/llvmcodebuilder.h +++ /dev/null @@ -1,204 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include -#include -#include -#include - -#include "icodebuilder.h" - -namespace libscratchcpp -{ - -class Target; - -class LLVMCodeBuilder : public ICodeBuilder -{ - public: - LLVMCodeBuilder(const std::string &id, bool warp); - - std::shared_ptr finalize() override; - - void addFunctionCall(const std::string &functionName, Compiler::StaticType returnType, const std::vector &argTypes) override; - void addConstValue(const Value &value) override; - void addVariableValue(Variable *variable) override; - void addListContents(List *list) override; - - void createAdd() override; - void createSub() override; - void createMul() override; - void createDiv() override; - - void createCmpEQ() override; - void createCmpGT() override; - void createCmpLT() override; - - void createAnd() override; - void createOr() override; - void createNot() override; - - void beginIfStatement() override; - void beginElseBranch() override; - void endIf() override; - - void beginRepeatLoop() override; - void beginWhileLoop() override; - void beginRepeatUntilLoop() override; - void beginLoopCondition() override; - void endLoop() override; - - void yield() override; - - private: - struct Register - { - Register(Compiler::StaticType type) : - type(type) - { - } - - Compiler::StaticType type = Compiler::StaticType::Void; - llvm::Value *value = nullptr; - bool isRawValue = false; - bool isConstValue = false; - Value constValue; - }; - - struct Step - { - enum class Type - { - FunctionCall, - Add, - Sub, - Mul, - Div, - CmpEQ, - CmpGT, - CmpLT, - And, - Or, - Not, - Yield, - BeginIf, - BeginElse, - EndIf, - BeginRepeatLoop, - BeginWhileLoop, - BeginRepeatUntilLoop, - BeginLoopCondition, - EndLoop - }; - - Step(Type type) : - type(type) - { - } - - Type type; - std::string functionName; - std::vector>> args; // target type, register - Compiler::StaticType functionReturnType = Compiler::StaticType::Void; - std::shared_ptr functionReturnReg; - }; - - 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; - }; - - struct Coroutine - { - llvm::Value *handle = nullptr; - llvm::BasicBlock *suspend = nullptr; - llvm::BasicBlock *cleanup = nullptr; - llvm::BasicBlock *freeMemRet = nullptr; - llvm::Value *didSuspend = nullptr; - }; - - struct Procedure - { - // TODO: Implement procedures - bool warp = false; - }; - - enum class Comparison - { - EQ, - GT, - LT - }; - - void initTypes(); - - Coroutine initCoroutine(llvm::Function *func); - void verifyFunction(llvm::Function *func); - void optimize(); - - void freeHeap(); - llvm::Value *castValue(std::shared_ptr reg, Compiler::StaticType targetType); - llvm::Value *castRawValue(std::shared_ptr reg, Compiler::StaticType targetType); - llvm::Constant *castConstValue(const Value &value, Compiler::StaticType targetType); - llvm::Type *getType(Compiler::StaticType type); - llvm::Value *isNaN(llvm::Value *num); - llvm::Value *removeNaN(llvm::Value *num); - - void createOp(Step::Type type, Compiler::StaticType retType, Compiler::StaticType argType, size_t argCount); - llvm::Value *createValue(std::shared_ptr reg); - llvm::Value *createComparison(std::shared_ptr arg1, std::shared_ptr arg2, Comparison type); - - llvm::FunctionCallee resolveFunction(const std::string name, llvm::FunctionType *type); - llvm::FunctionCallee resolve_value_init(); - llvm::FunctionCallee resolve_value_free(); - llvm::FunctionCallee resolve_value_assign_long(); - llvm::FunctionCallee resolve_value_assign_double(); - 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(); - llvm::FunctionCallee resolve_value_toCString(); - llvm::FunctionCallee resolve_value_doubleToCString(); - llvm::FunctionCallee resolve_value_boolToCString(); - llvm::FunctionCallee resolve_value_stringToDouble(); - llvm::FunctionCallee resolve_value_stringToBool(); - llvm::FunctionCallee resolve_value_equals(); - llvm::FunctionCallee resolve_value_greater(); - llvm::FunctionCallee resolve_value_lower(); - llvm::FunctionCallee resolve_strcasecmp(); - - std::string m_id; - llvm::LLVMContext m_ctx; - std::unique_ptr m_module; - llvm::IRBuilder<> m_builder; - - llvm::StructType *m_valueDataType = nullptr; - - std::vector m_steps; - size_t m_currentFunction = 0; - std::vector m_constValues; - std::vector>> m_regs; - std::vector> m_tmpRegs; - bool m_defaultWarp = false; - bool m_warp = false; - - std::vector m_heap; - - std::shared_ptr m_output; -}; - -} // namespace libscratchcpp diff --git a/src/dev/engine/promise.cpp b/src/dev/engine/promise.cpp new file mode 100644 index 00000000..4f6ed6ae --- /dev/null +++ b/src/dev/engine/promise.cpp @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "promise_p.h" + +using namespace libscratchcpp; + +/*! Constructs Promise. */ +Promise::Promise() : + impl(spimpl::make_unique_impl()) +{ +} + +/*! Returns true if the promise is resolved. */ +bool Promise::isResolved() const +{ + return impl->isResolved; +} + +/*! Marks the promise as resolved. */ +void Promise::resolve() +{ + impl->isResolved = true; +} diff --git a/src/dev/engine/promise_p.cpp b/src/dev/engine/promise_p.cpp new file mode 100644 index 00000000..bf4f7f0a --- /dev/null +++ b/src/dev/engine/promise_p.cpp @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include "promise_p.h" + +using namespace libscratchcpp; diff --git a/src/dev/engine/promise_p.h b/src/dev/engine/promise_p.h new file mode 100644 index 00000000..1c956537 --- /dev/null +++ b/src/dev/engine/promise_p.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +namespace libscratchcpp +{ + +struct PromisePrivate +{ + bool isResolved = false; +}; + +} // namespace libscratchcpp diff --git a/src/dev/test/CMakeLists.txt b/src/dev/test/CMakeLists.txt new file mode 100644 index 00000000..d447cb2a --- /dev/null +++ b/src/dev/test/CMakeLists.txt @@ -0,0 +1,6 @@ +target_sources(scratchcpp + PRIVATE + scriptbuilder.cpp + scriptbuilder_p.cpp + scriptbuilder_p.h +) diff --git a/src/dev/test/scriptbuilder.cpp b/src/dev/test/scriptbuilder.cpp new file mode 100644 index 00000000..363aed7e --- /dev/null +++ b/src/dev/test/scriptbuilder.cpp @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scriptbuilder_p.h" + +using namespace libscratchcpp; +using namespace libscratchcpp::test; + +// TODO: Add support for return values captures when building multiple scripts +static std::unordered_map> captureLists; +static ScriptBuilder *currentScriptBuilder = nullptr; + +/*! Constructs ScriptBuilder. */ +ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr target, bool createHatBlock) : + impl(spimpl::make_unique_impl(engine, target)) +{ + // Create capture list + captureLists[this] = std::make_shared("", ""); + + // Add start hat block + if (createHatBlock) { + auto block = std::make_shared(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[currentScriptBuilder].get(), input); + return nullptr; + }); +} + +/*! Destroys ScriptBuilder. */ +ScriptBuilder::~ScriptBuilder() +{ + captureLists.erase(this); +} + +/*! Adds the given block to the script. */ +void ScriptBuilder::addBlock(std::shared_ptr 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) +{ + addBlock(std::make_shared("", 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 = takeBlock(); + 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(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(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 valueBlock) +{ + if (!impl->lastBlock) + return; + + auto block = valueBlock; + block->setParent(impl->lastBlock); + + while (block) { + block->setId(nextId()); + impl->inputBlocks.push_back(block); + + auto parent = block->parent(); + auto next = block->next(); + + if (parent && block != valueBlock) + parent->setNext(block); + + if (next) + next->setParent(block); + + block = next; + } + + auto input = std::make_shared(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(name, Input::Type::ObscuredShadow); + auto block = std::make_shared(nextId(), ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); }); + input->setValueBlock(block); + impl->inputBlocks.push_back(block); + impl->lastBlock->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 input = std::make_shared(name, Input::Type::Shadow); + impl->lastBlock->addInput(input); + + auto menu = std::make_shared(nextId(), impl->lastBlock->opcode() + "_menu"); + menu->setShadow(true); + impl->inputBlocks.push_back(menu); + input->setValueBlock(menu); + + auto field = std::make_shared(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(name, selectedValue); + impl->lastBlock->addField(field); +} + +/*! Adds an input pointing to an entity (variable, list, broadcast, etc.) */ +void ScriptBuilder::addEntityInput(const std::string &name, const std::string &entityName, InputValue::Type entityType, std::shared_ptr entity) +{ + if (!impl->lastBlock) + return; + + if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) { + entity->setId(nextId()); + impl->entities.push_back(entity); + } + + auto input = std::make_shared(name, Input::Type::Shadow); + input->setPrimaryValue(entityName); + input->primaryValue()->setValuePtr(entity); + input->primaryValue()->setType(entityType); + impl->lastBlock->addInput(input); +} + +/*! Adds a field pointing to an entity (variable, list, broadcast, etc.) */ +void ScriptBuilder::addEntityField(const std::string &name, std::shared_ptr entity) +{ + if (!impl->lastBlock) + return; + + if (std::find(impl->entities.begin(), impl->entities.end(), entity) == impl->entities.end()) { + entity->setId(nextId()); + impl->entities.push_back(entity); + } + + auto field = std::make_shared(name, Value(), entity); + impl->lastBlock->addField(field); +} + +/*! + * Returns the current block (can be used e. g. with a custom Compiler instance).\n + * The script is automatically built to set the compile function of the block. + * \note This method is not intended for building scripts, use build() for that. + */ +std::shared_ptr ScriptBuilder::currentBlock() +{ + if (!impl->lastBlock) + return nullptr; + + 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); + + std::vector> targets = impl->engine->targets(); + targets.erase(std::remove(targets.begin(), targets.end(), target), targets.end()); + impl->engine->setTargets(targets); + } + + 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() +{ + build(impl->target); +} + +/*! Runs the built script. */ +void ScriptBuilder::run() +{ + impl->engine->run(); +} + +/*! Returns the list of captured block return values. */ +List *ScriptBuilder::capturedValues() const +{ + return captureLists[this].get(); +} + +/*! + * 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 &builders) +{ + std::unordered_set 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) +{ + if (!impl->blocks.empty()) { + auto lastBlock = impl->blocks.back(); + lastBlock->setNext(block); + block->setParent(lastBlock); + } + + impl->blocks.push_back(block); +} + +void ScriptBuilder::build(std::shared_ptr target) +{ + 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> &blocks) +{ + auto targetBlocks = target->blocks(); + + for (auto block : blocks) { + if (std::find(targetBlocks.begin(), targetBlocks.end(), block) == targetBlocks.end()) + target->addBlock(block); + } +} + +void ScriptBuilder::addTargetToEngine(IEngine *engine, std::shared_ptr target) +{ + std::vector> targets = engine->targets(); + + if (std::find(targets.begin(), targets.end(), target) == targets.end()) { + targets.push_back(target); + engine->setTargets(targets); + } +} diff --git a/src/dev/test/scriptbuilder_p.cpp b/src/dev/test/scriptbuilder_p.cpp new file mode 100644 index 00000000..f2d3fa29 --- /dev/null +++ b/src/dev/test/scriptbuilder_p.cpp @@ -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) : + engine(engine), + target(target) +{ +} diff --git a/src/dev/test/scriptbuilder_p.h b/src/dev/test/scriptbuilder_p.h new file mode 100644 index 00000000..67688bd5 --- /dev/null +++ b/src/dev/test/scriptbuilder_p.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace libscratchcpp +{ + +class IEngine; +class Target; +class Block; +class Entity; +class List; + +} // namespace libscratchcpp + +namespace libscratchcpp::test +{ + +class ScriptBuilderPrivate +{ + public: + ScriptBuilderPrivate(IEngine *engine, std::shared_ptr target); + ScriptBuilderPrivate(const ScriptBuilderPrivate &) = delete; + + IEngine *engine = nullptr; + std::shared_ptr target; + std::shared_ptr lastBlock; + std::vector> blocks; + std::vector> inputBlocks; + std::vector> entities; + unsigned int blockId = 0; +}; + +} // namespace libscratchcpp::test diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 71402cef..d1b47737 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -16,9 +16,10 @@ target_sources(scratchcpp internal/iclock.h internal/timer.cpp internal/timer.h + internal/stacktimer.cpp + internal/stacktimer.h internal/randomgenerator.h internal/randomgenerator.cpp - internal/irandomgenerator.h ) if(NOT LIBSCRATCHCPP_USE_LLVM) diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp index 5a8dd135..c0bd3173 100644 --- a/src/engine/internal/engine.cpp +++ b/src/engine/internal/engine.cpp @@ -9,6 +9,7 @@ #include #ifdef USE_LLVM #include +#include #else #include #endif @@ -270,10 +271,14 @@ void Engine::compile() // Compile scripts to bytecode for (auto target : m_targets) { std::cout << "Compiling scripts in target " << target->name() << "..." << std::endl; -#ifndef USE_LLVM +#ifdef USE_LLVM + auto ctx = Compiler::createContext(this, target.get()); + m_compilerContexts[target.get()] = ctx; + Compiler compiler(ctx.get()); +#else std::unordered_map procedureBytecodeMap; -#endif Compiler compiler(this, target.get()); +#endif const auto &blocks = target->blocks(); for (auto block : blocks) { if (block->topLevel() && !block->isTopLevelReporter() && !block->shadow()) { @@ -322,6 +327,12 @@ void Engine::compile() for (const std::string &opcode : unsupportedBlocks) m_unsupportedBlocks.insert(opcode); + +#ifdef USE_LLVM + // Preoptimize to avoid lag when starting scripts for the first time + std::cout << "Optimizing target " << target->name() << "..." << std::endl; + compiler.preoptimize(); +#endif } // Compile monitor blocks to bytecode @@ -593,8 +604,16 @@ void Engine::step() } else { Thread *th = senderThread; - if (std::find_if(m_threads.begin(), m_threads.end(), [th](std::shared_ptr thread) { return thread.get() == th; }) != m_threads.end()) + if (std::find_if(m_threads.begin(), m_threads.end(), [th](std::shared_ptr thread) { return thread.get() == th; }) != m_threads.end()) { +#ifdef USE_LLVM + auto promise = th->promise(); + + if (promise) + promise->resolve(); +#else th->resolvePromise(); +#endif + } resolved.push_back(broadcast); resolvedThreads.push_back(th); @@ -1003,7 +1022,11 @@ void Engine::addHatBlock(IExtension *extension, const std::string &opcode) if (m_compileFunctions.find(extension) == m_compileFunctions.cend()) m_compileFunctions[extension] = {}; +#ifdef USE_LLVM + m_compileFunctions[extension][opcode] = [](Compiler *compiler) -> CompilerValue * { return nullptr; }; +#else m_compileFunctions[extension][opcode] = [](Compiler *compiler) {}; +#endif } void Engine::addInput(IExtension *extension, const std::string &name, int id) @@ -1513,10 +1536,12 @@ std::shared_ptr Engine::getVariable(const std::string &id, Target *tar int index; // Check stage - index = stage->findVariableById(id); + if (stage) { + index = stage->findVariableById(id); - if (index != -1) - return stage->variableAt(index); + if (index != -1) + return stage->variableAt(index); + } // Check currently compiled target if (target != stage) { @@ -1549,10 +1574,12 @@ std::shared_ptr Engine::getList(const std::string &id, Target *target) int index; // Check stage - index = stage->findListById(id); + if (stage) { + index = stage->findListById(id); - if (index != -1) - return stage->listAt(index); + if (index != -1) + return stage->listAt(index); + } // Check currently compiled target if (target != stage) { @@ -2014,8 +2041,16 @@ void Engine::addBroadcastPromise(Broadcast *broadcast, Thread *sender, bool wait // Resolve broadcast promise if it's already running auto it = m_broadcastSenders.find(broadcast); - if (it != m_broadcastSenders.cend() && std::find_if(m_threads.begin(), m_threads.end(), [&it](std::shared_ptr thread) { return thread.get() == it->second; }) != m_threads.end()) + if (it != m_broadcastSenders.cend() && std::find_if(m_threads.begin(), m_threads.end(), [&it](std::shared_ptr thread) { return thread.get() == it->second; }) != m_threads.end()) { +#ifdef USE_LLVM + auto promise = it->second->promise(); + + if (promise) + promise->resolve(); +#else it->second->resolvePromise(); +#endif + } if (wait) m_broadcastSenders[broadcast] = sender; diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h index 69d64835..ed4b562d 100644 --- a/src/engine/internal/engine.h +++ b/src/engine/internal/engine.h @@ -19,6 +19,9 @@ class Entity; class IClock; class IAudioEngine; class Thread; +#ifdef USE_LLVM +class CompilerContext; +#endif class Engine : public IEngine { @@ -238,6 +241,9 @@ class Engine : public IEngine static const std::unordered_map m_hatEdgeActivated; // used to check whether a hat is edge-activated (runs when a predicate becomes true) std::vector> m_targets; +#ifdef USE_LLVM + std::unordered_map> m_compilerContexts; +#endif std::vector> m_broadcasts; std::unordered_map> m_broadcastMap; std::unordered_map> m_backdropBroadcastMap; diff --git a/src/engine/internal/irandomgenerator.h b/src/engine/internal/irandomgenerator.h deleted file mode 100644 index 2228c933..00000000 --- a/src/engine/internal/irandomgenerator.h +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -namespace libscratchcpp -{ - -class IRandomGenerator -{ - public: - virtual ~IRandomGenerator() { } - - virtual long randint(long start, long end) const = 0; - virtual double randintDouble(double start, double end) const = 0; - virtual long randintExcept(long start, long end, long except) const = 0; -}; - -} // namespace libscratchcpp diff --git a/src/engine/internal/randomgenerator.cpp b/src/engine/internal/randomgenerator.cpp index e9e7c572..9f441238 100644 --- a/src/engine/internal/randomgenerator.cpp +++ b/src/engine/internal/randomgenerator.cpp @@ -57,13 +57,12 @@ long RandomGenerator::randintExcept(long start, long end, long except) const return end; else return start; - } - - if (randint(0, 1) == 0) { - std::uniform_int_distribution distribution(start, except - 1); - return distribution(*m_generator); - } else { - std::uniform_int_distribution distribution(except + 1, end); - return distribution(*m_generator); - } + } else if (except == start) + return randint(except + 1, end); + else if (except == end) + return randint(start, except - 1); + else if (randint(0, 1) == 0) + return randint(start, except - 1); + else + return randint(except + 1, end); } diff --git a/src/engine/internal/randomgenerator.h b/src/engine/internal/randomgenerator.h index 1358de37..1db28b69 100644 --- a/src/engine/internal/randomgenerator.h +++ b/src/engine/internal/randomgenerator.h @@ -2,11 +2,10 @@ #pragma once +#include #include #include -#include "irandomgenerator.h" - namespace libscratchcpp { @@ -20,7 +19,7 @@ class RandomGenerator : public IRandomGenerator long randint(long start, long end) const override; double randintDouble(double start, double end) const override; - long randintExcept(long start, long end, long except) const; + long randintExcept(long start, long end, long except) const override; private: static std::shared_ptr m_instance; diff --git a/src/engine/internal/stacktimer.cpp b/src/engine/internal/stacktimer.cpp new file mode 100644 index 00000000..01ae3bc7 --- /dev/null +++ b/src/engine/internal/stacktimer.cpp @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "stacktimer.h" +#include "clock.h" + +using namespace libscratchcpp; + +StackTimer::StackTimer() +{ + m_clock = Clock::instance().get(); +} + +StackTimer::StackTimer(IClock *clock) : + m_clock(clock) +{ + assert(clock); +} + +void StackTimer::start(double seconds) +{ + m_startTime = m_clock->currentSteadyTime(); + m_timeLimit = seconds * 1000; + m_stopped = false; +} + +void StackTimer::stop() +{ + m_stopped = true; +} + +bool StackTimer::stopped() const +{ + return m_stopped; +} + +bool StackTimer::elapsed() const +{ + if (m_stopped) + return false; + + return std::chrono::duration_cast(m_clock->currentSteadyTime() - m_startTime).count() >= m_timeLimit; +} diff --git a/src/engine/internal/stacktimer.h b/src/engine/internal/stacktimer.h new file mode 100644 index 00000000..54ece0c8 --- /dev/null +++ b/src/engine/internal/stacktimer.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include +#include + +namespace libscratchcpp +{ + +class IClock; + +class StackTimer : public IStackTimer +{ + public: + StackTimer(); + StackTimer(IClock *clock); + StackTimer(const StackTimer &) = delete; + + void start(double seconds) override; + void stop() override; + + bool stopped() const override; + bool elapsed() const override; + + private: + std::chrono::steady_clock::time_point m_startTime; + bool m_stopped = true; + long m_timeLimit = 0; + IClock *m_clock = nullptr; +}; + +} // namespace libscratchcpp diff --git a/src/engine/thread.cpp b/src/engine/thread.cpp index 2ffe2e56..bf34cdab 100644 --- a/src/engine/thread.cpp +++ b/src/engine/thread.cpp @@ -5,6 +5,7 @@ #ifdef USE_LLVM #include #include +#include #endif #include "thread_p.h" @@ -17,8 +18,12 @@ Thread::Thread(Target *target, IEngine *engine, Script *script) : { impl->vm = std::make_unique(target, engine, script, this); #ifdef USE_LLVM - impl->code = impl->script->code(); - impl->executionContext = impl->code->createExecutionContext(target); + if (impl->script) { + impl->code = impl->script->code(); + + if (impl->code) + impl->executionContext = impl->code->createExecutionContext(this); + } #endif } @@ -87,22 +92,28 @@ bool Thread::isFinished() const #endif } +#ifdef USE_LLVM +/*! Returns the script promise. */ +std::shared_ptr Thread::promise() const +{ + return impl->executionContext->promise(); +} + +/*! Sets the script promise (yields until the promise is resolved). */ +void Thread::setPromise(std::shared_ptr promise) +{ + impl->executionContext->setPromise(promise); +} +#else /*! Pauses the script (when it's executed using run() again) until resolvePromise() is called. */ void Thread::promise() { -#ifdef USE_LLVM - impl->code->promise(); -#else impl->vm->promise(); -#endif } /*! Resolves the promise and resumes the script. */ void Thread::resolvePromise() { -#ifdef USE_LLVM - impl->code->resolvePromise(); -#else impl->vm->resolvePromise(); -#endif } +#endif // USE_LLVM diff --git a/src/internal/projectdownloader.cpp b/src/internal/projectdownloader.cpp index c334da55..28a618d5 100644 --- a/src/internal/projectdownloader.cpp +++ b/src/internal/projectdownloader.cpp @@ -17,14 +17,11 @@ static const std::string ASSET_PREFIX = "https://assets.scratch.mit.edu/internal static const std::string ASSET_SUFFIX = "/get"; #define CHECK_CANCEL() \ - m_cancelMutex.lock(); \ if (m_cancel) { \ m_downloadedAssetCount = 0; \ - m_cancelMutex.unlock(); \ std::cout << "Download aborted!" << std::endl; \ return false; \ - } \ - m_cancelMutex.unlock() + } ProjectDownloader::ProjectDownloader(IDownloaderFactory *downloaderFactory) : m_downloaderFactory(downloaderFactory) @@ -38,9 +35,7 @@ ProjectDownloader::ProjectDownloader(IDownloaderFactory *downloaderFactory) : bool ProjectDownloader::downloadJson(const std::string &projectId) { - m_cancelMutex.lock(); m_cancel = false; - m_cancelMutex.unlock(); // Get project token std::cout << "Fetching project info of " << projectId << std::endl; @@ -89,9 +84,7 @@ bool ProjectDownloader::downloadJson(const std::string &projectId) bool ProjectDownloader::downloadAssets(const std::vector &assetIds) { - m_cancelMutex.lock(); m_cancel = false; - m_cancelMutex.unlock(); auto count = assetIds.size(); // unsigned int threadCount = std::thread::hardware_concurrency(); @@ -119,20 +112,14 @@ bool ProjectDownloader::downloadAssets(const std::vector &assetIds) // Download assets auto f = [this, count](std::shared_ptr downloader, int index, const std::string &id) { - m_cancelMutex.lock(); - if (m_cancel) return; - m_cancelMutex.unlock(); - bool ret = downloader->download(ASSET_PREFIX + id + ASSET_SUFFIX); if (!ret) { std::cerr << "Failed to download asset: " << id << std::endl; - m_cancelMutex.lock(); m_cancel = true; - m_cancelMutex.unlock(); return; } @@ -178,7 +165,7 @@ bool ProjectDownloader::downloadAssets(const std::vector &assetIds) m_assetsMutex.lock(); - if (m_downloadedAssetCount != lastCount) { + if (m_downloadedAssetCount != lastCount && !m_cancel) { std::cout << "Downloaded assets: " << m_downloadedAssetCount << " of " << count << std::endl; lastCount = m_downloadedAssetCount; } @@ -197,7 +184,7 @@ bool ProjectDownloader::downloadAssets(const std::vector &assetIds) threads.erase(index); } - if (done) { + if (done || m_cancel) { for (auto &[index, info] : threads) info.first.join(); @@ -212,9 +199,7 @@ bool ProjectDownloader::downloadAssets(const std::vector &assetIds) void ProjectDownloader::cancel() { - m_cancelMutex.lock(); m_cancel = true; - m_cancelMutex.unlock(); m_downloadedAssetCount = 0; } diff --git a/src/internal/projectdownloader.h b/src/internal/projectdownloader.h index 15b47472..07893458 100644 --- a/src/internal/projectdownloader.h +++ b/src/internal/projectdownloader.h @@ -36,8 +36,7 @@ class ProjectDownloader : public IProjectDownloader std::vector m_assets; std::mutex m_assetsMutex; std::atomic m_downloadedAssetCount = 0; - bool m_cancel = false; - std::mutex m_cancelMutex; + std::atomic m_cancel = false; sigslot::signal m_downloadProgressChanged; }; diff --git a/src/project.cpp b/src/project.cpp index 221d8f1a..80bac15b 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 #include +#include #include "project_p.h" +#include "internal/projectdownloader.h" using namespace libscratchcpp; @@ -27,6 +29,14 @@ bool Project::load() return impl->load(); } +/*! Cancels project loading if loading in another thread. */ +void Project::stopLoading() +{ + std::cout << "Aborting project loading..." << std::endl; + impl->stopLoading = true; + impl->downloader->cancel(); +} + /*! * Calls all "when green flag clicked" blocks. * \note Nothing will happen until run() or frame() is called. diff --git a/src/project_p.cpp b/src/project_p.cpp index e3450fb4..c8c40e20 100644 --- a/src/project_p.cpp +++ b/src/project_p.cpp @@ -47,6 +47,8 @@ bool ProjectPrivate::load() bool ProjectPrivate::tryLoad(IProjectReader *reader) { + stopLoading = false; + // Load from URL ProjectUrl url(fileName); @@ -57,8 +59,18 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader) return false; } + if (stopLoading) { + loadingAborted(); + return false; + } + bool ret = reader->loadData(downloader->json()); + if (stopLoading) { + loadingAborted(); + return false; + } + if (!ret) return false; @@ -91,7 +103,14 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader) } // Download assets - if (!downloader->downloadAssets(assetNames)) { + ret = downloader->downloadAssets(assetNames); + + if (stopLoading) { + loadingAborted(); + return false; + } + + if (!ret) { std::cerr << "Failed to download the project assets." << std::endl; return false; } @@ -113,7 +132,18 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader) return false; } + if (stopLoading) { + loadingAborted(); + return false; + } + bool ret = reader->load(); + + if (stopLoading) { + loadingAborted(); + return false; + } + if (!ret) return false; } @@ -125,9 +155,20 @@ bool ProjectPrivate::tryLoad(IProjectReader *reader) engine->setExtensions(reader->extensions()); engine->setUserAgent(reader->userAgent()); engine->compile(); + + if (stopLoading) { + loadingAborted(); + return false; + } + return true; } +void ProjectPrivate::loadingAborted() +{ + std::cout << "Loading aborted." << std::endl; +} + void ProjectPrivate::start() { engine->start(); diff --git a/src/project_p.h b/src/project_p.h index d935835c..cb937f3a 100644 --- a/src/project_p.h +++ b/src/project_p.h @@ -21,6 +21,7 @@ struct ProjectPrivate bool load(); bool tryLoad(IProjectReader *reader); + void loadingAborted(); void start(); void run(); @@ -29,6 +30,7 @@ struct ProjectPrivate sigslot::signal &downloadProgressChanged(); std::string fileName; + std::atomic stopLoading = false; std::shared_ptr engine = nullptr; static IProjectDownloaderFactory *downloaderFactory; diff --git a/src/scratch/CMakeLists.txt b/src/scratch/CMakeLists.txt index 926cb2b0..71d6c8c0 100644 --- a/src/scratch/CMakeLists.txt +++ b/src/scratch/CMakeLists.txt @@ -14,6 +14,8 @@ target_sources(scratchcpp list.cpp list_p.cpp list_p.h + list_functions.cpp + list_functions.h block.cpp block_p.cpp block_p.h diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp index bad195a4..79ffd4b1 100644 --- a/src/scratch/asset.cpp +++ b/src/scratch/asset.cpp @@ -58,3 +58,15 @@ void Asset::setData(unsigned int size, void *data) impl->data = data; processData(size, data); } + +/*! Returns the sprite or stage this asset belongs to. */ +Target *Asset::target() const +{ + return impl->target; +} + +/*! Sets the sprite or stage this asset belongs to. */ +void Asset::setTarget(Target *target) +{ + impl->target = target; +} diff --git a/src/scratch/asset_p.h b/src/scratch/asset_p.h index 81cd3219..d004fb2f 100644 --- a/src/scratch/asset_p.h +++ b/src/scratch/asset_p.h @@ -7,6 +7,8 @@ namespace libscratchcpp { +class Target; + struct AssetPrivate { AssetPrivate(const std::string &name, const std::string &format); @@ -19,6 +21,7 @@ struct AssetPrivate std::string fileName; const void *data = nullptr; unsigned int dataSize = 0; + Target *target = nullptr; }; } // namespace libscratchcpp diff --git a/src/scratch/block.cpp b/src/scratch/block.cpp index 0350ccd4..8e014bc1 100644 --- a/src/scratch/block.cpp +++ b/src/scratch/block.cpp @@ -4,6 +4,10 @@ #include #include #include +#ifdef USE_LLVM +#include +#include +#endif #include #include "block_p.h" @@ -17,12 +21,23 @@ Block::Block(const std::string &id, const std::string &opcode) : { } +#ifdef USE_LLVM +/*! Calls the compile function. */ +CompilerValue *Block::compile(Compiler *compiler) +{ + if (impl->compileFunction) + return impl->compileFunction(compiler); + else + return nullptr; +} +#else /*! Calls the compile function. */ void Block::compile(Compiler *compiler) { if (impl->compileFunction) return impl->compileFunction(compiler); } +#endif /*! Returns the opcode. */ const std::string &Block::opcode() const @@ -43,7 +58,7 @@ void Block::setCompileFunction(BlockComp newCompileFunction) } /*! Returns the edge-activated hat predicate compile function. \see Block sections */ -BlockComp Block::hatPredicateCompileFunction() const +HatPredicateCompileFunc Block::hatPredicateCompileFunction() const { return impl->hatPredicateCompileFunction; } diff --git a/src/scratch/blockprototype_p.cpp b/src/scratch/blockprototype_p.cpp index 3d780502..bfbba295 100644 --- a/src/scratch/blockprototype_p.cpp +++ b/src/scratch/blockprototype_p.cpp @@ -27,6 +27,7 @@ void BlockPrototypePrivate::setProcCode(const std::string &newProcCode) arg = false; switch (c) { case 's': + case 'n': argumentDefaults.push_back(""); argumentTypes.push_back(BlockPrototype::ArgType::StringNum); break; diff --git a/src/scratch/inputvalue.cpp b/src/scratch/inputvalue.cpp index 2d66269d..03edf80a 100644 --- a/src/scratch/inputvalue.cpp +++ b/src/scratch/inputvalue.cpp @@ -3,6 +3,7 @@ #include #ifdef USE_LLVM #include +#include #else #include #endif @@ -26,55 +27,68 @@ InputValue::InputValue(Type type) : { } +#ifdef USE_LLVM /*! Compiles the input value. */ -void InputValue::compile(Compiler *compiler) +CompilerValue *InputValue::compile(Compiler *compiler) { switch (impl->type) { case Type::Color: // TODO: Add support for colors - break; + return nullptr; case Type::Variable: { assert(impl->valuePtr); -#ifdef USE_LLVM Variable *var = dynamic_cast(impl->valuePtr.get()); if (var) - compiler->addVariableValue(var); + return compiler->addVariableValue(var); else - compiler->addConstValue(Value()); -#else - compiler->addInstruction(vm::OP_READ_VAR, { compiler->variableIndex(impl->valuePtr) }); -#endif - - break; + return compiler->addConstValue(Value()); } case Type::List: { assert(impl->valuePtr); -#ifdef USE_LLVM List *list = dynamic_cast(impl->valuePtr.get()); if (list) - compiler->addListContents(list); + return compiler->addListContents(list); else - compiler->addConstValue(Value()); + return compiler->addConstValue(Value()); + } + + default: + return compiler->addConstValue(impl->value); + } +} #else +/*! Compiles the input value. */ +void InputValue::compile(Compiler *compiler) +{ + switch (impl->type) { + case Type::Color: + // TODO: Add support for colors + break; + + case Type::Variable: { + assert(impl->valuePtr); + compiler->addInstruction(vm::OP_READ_VAR, { compiler->variableIndex(impl->valuePtr) }); + + break; + } + + case Type::List: { + assert(impl->valuePtr); compiler->addInstruction(vm::OP_READ_LIST, { compiler->listIndex(impl->valuePtr) }); -#endif break; } default: -#ifdef USE_LLVM - compiler->addConstValue(impl->value); -#else compiler->addInstruction(vm::OP_CONST, { compiler->constIndex(this) }); -#endif break; } } +#endif // USE_LLVM /*! Returns the type of the value. */ InputValue::Type InputValue::type() const diff --git a/src/scratch/list_functions.cpp b/src/scratch/list_functions.cpp new file mode 100644 index 00000000..e5b02441 --- /dev/null +++ b/src/scratch/list_functions.cpp @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 + +#include + +#include "list_functions.h" + +using namespace libscratchcpp; + +extern "C" +{ + void list_clear(List *list) + { + list->clear(); + } + + void list_remove(List *list, size_t index) + { + list->removeAt(index); + } + + ValueData *list_append_empty(List *list) + { + return &list->appendEmpty(); + } + + ValueData *list_insert_empty(List *list, size_t index) + { + return &list->insertEmpty(index); + } + + ValueData *list_get_item(List *list, size_t index) + { + return &list->operator[](index); + } + + ValueData *list_data(List *list) + { + return list->data(); + } + + size_t *list_size_ptr(List *list) + { + return list->sizePtr(); + } + + const size_t *list_alloc_size_ptr(List *list) + { + return list->allocatedSizePtr(); + } + + size_t list_size(List *list) + { + return list->size(); + } + + char *list_to_string(List *list) + { + std::string str; + list->toString(str); + + char *ret = (char *)malloc((str.size() + 1) * sizeof(char)); + strncpy(ret, str.c_str(), str.size() + 1); + return ret; + } +} diff --git a/src/scratch/list_functions.h b/src/scratch/list_functions.h new file mode 100644 index 00000000..64398605 --- /dev/null +++ b/src/scratch/list_functions.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace libscratchcpp +{ + +class List; +struct ValueData; + +extern "C" +{ + void list_clear(List *list); + void list_remove(List *list, size_t index); + + ValueData *list_append_empty(List *list); + ValueData *list_insert_empty(List *list, size_t index); + + ValueData *list_get_item(List *list, size_t index); + ValueData *list_data(List *list); + size_t *list_size_ptr(List *list); + const size_t *list_alloc_size_ptr(List *list); + size_t list_size(List *list); + + char *list_to_string(List *list); +} + +} // namespace libscratchcpp diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp index eca5e8b9..64fcf35a 100644 --- a/src/scratch/sound.cpp +++ b/src/scratch/sound.cpp @@ -87,18 +87,6 @@ bool Sound::isPlaying() const return impl->player->isPlaying(); } -/*! Returns the sprite or stage this variable belongs to. */ -Target *Sound::target() const -{ - return impl->target; -} - -/*! Sets the sprite or stage this variable belongs to. */ -void Sound::setTarget(Target *target) -{ - impl->target = target; -} - /*! Returns an independent copy of the sound which is valid for as long as the original sound exists. */ std::shared_ptr Sound::clone() const { @@ -130,8 +118,10 @@ void Sound::processData(unsigned int size, void *data) void Sound::stopCloneSounds() { - if (impl->target && !impl->target->isStage()) { - Sprite *sprite = static_cast(impl->target); + Target *target = this->target(); + + if (target && !target->isStage()) { + Sprite *sprite = static_cast(target); if (sprite->isClone()) sprite = sprite->cloneSprite(); diff --git a/src/scratch/sound_p.h b/src/scratch/sound_p.h index 3df2ca3f..9f2b460c 100644 --- a/src/scratch/sound_p.h +++ b/src/scratch/sound_p.h @@ -10,7 +10,6 @@ namespace libscratchcpp { -class Target; class Sound; struct SoundPrivate @@ -22,7 +21,6 @@ struct SoundPrivate int sampleCount = 0; static IAudioOutput *audioOutput; std::shared_ptr player = nullptr; - Target *target = nullptr; const Sound *cloneRoot = nullptr; }; diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp index 5cf80e68..b755ca26 100644 --- a/src/scratch/sprite.cpp +++ b/src/scratch/sprite.cpp @@ -504,7 +504,7 @@ bool Sprite::touchingPoint(double x, double y) const } /*! Overrides Target#touchingColor(). */ -bool Sprite::touchingColor(const Value &color) const +bool Sprite::touchingColor(Rgb color) const { if (!impl->iface) return false; @@ -513,7 +513,7 @@ bool Sprite::touchingColor(const Value &color) const } /*! Overrides Target#touchingColor(). */ -bool Sprite::touchingColor(const Value &color, const Value &mask) const +bool Sprite::touchingColor(Rgb color, Rgb mask) const { if (!impl->iface) return false; @@ -579,12 +579,8 @@ void Sprite::setXY(double x, double y) } else impl->getFencedPosition(x, y, &impl->x, &impl->y); - if (impl->visible) { - IEngine *eng = engine(); - - if (eng) - eng->requestRedraw(); - } + if (eng && impl->visible) + eng->requestRedraw(); if (impl->iface) impl->iface->onMoved(oldX, oldY, impl->x, impl->y); diff --git a/src/scratch/stage.cpp b/src/scratch/stage.cpp index 3a755886..124e6ff8 100644 --- a/src/scratch/stage.cpp +++ b/src/scratch/stage.cpp @@ -186,7 +186,7 @@ bool Stage::touchingPoint(double x, double y) const } /*! Overrides Target#touchingColor(). */ -bool Stage::touchingColor(const Value &color) const +bool Stage::touchingColor(Rgb color) const { if (!impl->iface) return false; @@ -195,7 +195,7 @@ bool Stage::touchingColor(const Value &color) const } /*! Overrides Target#touchingColor(). */ -bool Stage::touchingColor(const Value &color, const Value &mask) const +bool Stage::touchingColor(Rgb color, Rgb mask) const { if (!impl->iface) return false; diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp index 7d01cebe..4fc276aa 100644 --- a/src/scratch/target.cpp +++ b/src/scratch/target.cpp @@ -29,6 +29,16 @@ Target::Target() : { } +/*! Destroys Target. */ +Target::~Target() +{ + if (impl->variableData) + free(impl->variableData); + + if (impl->listData) + free(impl->listData); +} + /*! Returns true. */ bool Target::isTarget() const { @@ -66,6 +76,7 @@ int Target::addVariable(std::shared_ptr variable) return it - impl->variables.begin(); impl->variables.push_back(variable); + impl->variableDataDirty = true; variable->setTarget(this); return impl->variables.size() - 1; @@ -102,6 +113,31 @@ int Target::findVariableById(const std::string &id) const return it - impl->variables.begin(); } +/*! Returns an array of raw variable data pointers (for optimized variable access). */ +ValueData **Target::variableData() +{ + if (impl->variableDataDirty) { + const size_t len = impl->variables.size(); + + if (len == 0) { + impl->variableDataDirty = false; + return nullptr; + } + + if (impl->variableData) + impl->variableData = (ValueData **)realloc(impl->variableData, len * sizeof(ValueData *)); + else + impl->variableData = (ValueData **)malloc(len * sizeof(ValueData *)); + + for (size_t i = 0; i < len; i++) + impl->variableData[i] = &impl->variables[i]->valuePtr()->data(); + + impl->variableDataDirty = false; + } + + return impl->variableData; +} + /*! Returns the list of Scratch lists. */ const std::vector> &Target::lists() const { @@ -117,6 +153,7 @@ int Target::addList(std::shared_ptr list) return it - impl->lists.begin(); impl->lists.push_back(list); + impl->listDataDirty = true; list->setTarget(this); return impl->lists.size() - 1; @@ -153,6 +190,31 @@ int Target::findListById(const std::string &id) const return it - impl->lists.begin(); } +/*! Returns an array of list pointers (for optimized list access). */ +List **Target::listData() +{ + if (impl->listDataDirty) { + const size_t len = impl->lists.size(); + + if (len == 0) { + impl->listDataDirty = false; + return nullptr; + } + + if (impl->listData) + impl->listData = (List **)realloc(impl->listData, len * sizeof(List *)); + else + impl->listData = (List **)malloc(len * sizeof(List *)); + + for (size_t i = 0; i < len; i++) + impl->listData[i] = impl->lists[i].get(); + + impl->listDataDirty = false; + } + + return impl->listData; +} + /*! Returns the list of blocks. */ const std::vector> &Target::blocks() const { @@ -526,13 +588,13 @@ bool Target::touchingEdge() const } /*! Returns true if the Target is touching the given color (RGB triplet). */ -bool Target::touchingColor(const Value &color) const +bool Target::touchingColor(Rgb color) const { return false; } /*! Returns true if the mask part of the Target is touching the given color (RGB triplet). */ -bool Target::touchingColor(const Value &color, const Value &mask) const +bool Target::touchingColor(Rgb color, Rgb mask) const { return false; } diff --git a/src/scratch/target_p.h b/src/scratch/target_p.h index eaf6206e..1261330a 100644 --- a/src/scratch/target_p.h +++ b/src/scratch/target_p.h @@ -28,7 +28,11 @@ struct TargetPrivate std::string name; std::vector> variables; + bool variableDataDirty = true; + ValueData **variableData = nullptr; std::vector> lists; + bool listDataDirty = true; + List **listData = nullptr; std::vector> blocks; std::vector> comments; int costumeIndex = -1; diff --git a/src/scratch/value_functions.cpp b/src/scratch/value_functions.cpp index b35092d0..7dced357 100644 --- a/src/scratch/value_functions.cpp +++ b/src/scratch/value_functions.cpp @@ -164,14 +164,9 @@ extern "C" case ValueType::Bool: return true; - case ValueType::Number: { - if (std::isinf(v->numberValue) || std::isnan(v->numberValue)) - return true; + case ValueType::Number: + return value_doubleIsInt(v->numberValue); - double intpart; - std::modf(v->numberValue, &intpart); - return v->numberValue == intpart; - } case ValueType::String: return value_checkString(v->stringValue) == 1; } @@ -197,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) @@ -210,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) @@ -247,16 +242,11 @@ extern "C" } /*! Writes the string representation of the given value to dst. */ - void value_toString(const libscratchcpp::ValueData *v, std::string *dst) + void value_toString(const ValueData *v, std::string *dst) { - if (v->type == ValueType::String) - dst->assign(v->stringValue); - else if (v->type == ValueType::Number) - value_doubleToString(v->numberValue, dst); - else if (v->type == ValueType::Bool) - dst->assign(v->boolValue ? "true" : "false"); - else - dst->clear(); + char *str = value_toCString(v); + dst->assign(str); + free(str); } /*! @@ -265,11 +255,29 @@ extern "C" */ char *value_toCString(const ValueData *v) { - std::string out; - value_toString(v, &out); - char *ret = (char *)malloc((out.size() + 1) * sizeof(char)); - strncpy(ret, out.c_str(), out.size() + 1); - return ret; + if (v->type == ValueType::String) { + char *ret = (char *)malloc((strlen(v->stringValue) + 1) * sizeof(char)); + strcpy(ret, v->stringValue); + return ret; + } else if (v->type == ValueType::Number) + return value_doubleToCString(v->numberValue); + else if (v->type == ValueType::Bool) { + char *ret; + + if (v->boolValue) { + ret = (char *)malloc((4 + 1) * sizeof(char)); + strcpy(ret, "true"); + } else { + ret = (char *)malloc((5 + 1) * sizeof(char)); + strcpy(ret, "false"); + } + + return ret; + } else { + char *ret = (char *)malloc(sizeof(char)); + ret[0] = '\0'; + return ret; + } } /*! Writes the UTF-16 representation of the given value to dst. */ @@ -280,17 +288,148 @@ extern "C" dst->assign(utf8::utf8to16(s)); } + /*! Returns the RGBA quadruplet from the given color value. */ + Rgb value_toRgba(const ValueData *v) + { + // https://github.com/scratchfoundation/scratch-vm/blob/112989da0e7306eeb405a5c52616e41c2164af24/src/util/cast.js#L92-L103 + char *string = nullptr; + size_t stringLen = 0; + + if (v->type == ValueType::Number) + return v->numberValue; + else if (v->type == ValueType::String) { + string = value_toCString(v); + stringLen = strlen(string); + } else if (v->type == ValueType::Bool) + return v->boolValue; + + if (stringLen > 0 && string[0] == '#') { + // https://github.com/scratchfoundation/scratch-vm/blob/a4f095db5e03e072ba222fe721eeeb543c9b9c15/src/util/color.js#L60-L69 + // (this implementation avoids regex) + + // Handle shorthand hex (e.g., "abc" -> "aabbcc") + char expandedHex[7] = { 0 }; + char *ptr; + + if (stringLen == 4) { + expandedHex[0] = string[1]; + expandedHex[1] = string[1]; + expandedHex[2] = string[2]; + expandedHex[3] = string[2]; + expandedHex[4] = string[3]; + expandedHex[5] = string[3]; + ptr = expandedHex; + } else if (stringLen == 7) + ptr = string + 1; // skip '#' + else { + free(string); + return rgb(0, 0, 0); + } + + // Convert hex components to integers + int r, g, b; + + if (std::sscanf(ptr, "%2x%2x%2x", &r, &g, &b) == 3) { + free(string); + return rgb(r, g, b); + } + + free(string); + } else if (stringLen > 0) { + const double ret = value_stringToDouble(string); + free(string); + return ret; + } else if (string) + free(string); + + return rgb(0, 0, 0); + } + + /*! Returns true if the given number represents a round integer. */ + bool value_doubleIsInt(double v) + { + if (std::isinf(v) || std::isnan(v)) + return true; + + double intpart; + std::modf(v, &intpart); + return v == intpart; + } + /*! * Converts the given number to string. * \note It is the caller's responsibility to free allocated memory. */ char *value_doubleToCString(double v) { - std::string out; - value_doubleToString(v, &out); - char *ret = (char *)malloc((out.size() + 1) * sizeof(char)); - strncpy(ret, out.c_str(), out.size() + 1); - return ret; + if (v == 0) { + char *ret = (char *)malloc((1 + 1) * sizeof(char)); + strcpy(ret, "0"); + return ret; + } else if (std::isinf(v)) { + if (v > 0) { + char *ret = (char *)malloc((8 + 1) * sizeof(char)); + strcpy(ret, "Infinity"); + return ret; + } else { + char *ret = (char *)malloc((9 + 1) * sizeof(char)); + strcpy(ret, "-Infinity"); + return ret; + } + } else if (std::isnan(v)) { + char *ret = (char *)malloc((3 + 1) * sizeof(char)); + strcpy(ret, "NaN"); + return ret; + } + + const int maxlen = 26; // should be enough for any number + char *buffer = (char *)malloc((maxlen + 1) * sizeof(char)); + + // Constants for significant digits and thresholds + const int significantDigits = 17 - value_intDigitCount(std::floor(std::fabs(v))); + constexpr double scientificThreshold = 1e21; + constexpr double minScientificThreshold = 1e-6; + + // Use scientific notation if the number is very large or very small + if (std::fabs(v) >= scientificThreshold || std::fabs(v) < minScientificThreshold) { + int ret = snprintf(buffer, maxlen, "%.*g", significantDigits - 1, v); + assert(ret >= 0); + } else { + snprintf(buffer, maxlen, "%.*f", significantDigits - 1, v); + + // Remove trailing zeros from the fractional part + char *dot = std::strchr(buffer, '.'); + + if (dot) { + char *end = buffer + std::strlen(buffer) - 1; + while (end > dot && *end == '0') { + *end-- = '\0'; + } + if (*end == '.') { + *end = '\0'; // Remove trailing dot + } + } + } + + // Remove leading zeros after e+/e- + for (int i = 0; i < 2; i++) { + const char *target = (i == 0) ? "e+" : "e-"; + char *index = strstr(buffer, target); + + if (index != nullptr) { + char *ptr = index + 2; + while (*(ptr + 1) != '\0' && *ptr == '0') { + // Shift the characters left to erase '0' + char *shiftPtr = ptr; + do { + *shiftPtr = *(shiftPtr + 1); + shiftPtr++; + } while (*shiftPtr != '\0'); + } + } + } + + return buffer; } /*! @@ -354,17 +493,9 @@ extern "C" /*! Calculates the modulo the given values and writes the result to dst. */ void value_mod(const libscratchcpp::ValueData *v1, const libscratchcpp::ValueData *v2, ValueData *dst) { - double a = value_toDouble(v1); - double b = value_toDouble(v2); - - if ((b == 0) || std::isinf(a)) - value_assign_double(dst, std::numeric_limits::quiet_NaN()); - else if (std::isinf(b)) - value_assign_double(dst, value_toDouble(v1)); - else if (value_isNegative(v1) || value_isNegative(v2)) - value_assign_double(dst, fmod(value_toDouble(v2) + fmod(value_toDouble(v1), -value_toDouble(v2)), value_toDouble(v2))); - else - value_assign_double(dst, fmod(value_toDouble(v1), value_toDouble(v2))); + const double a = value_toDouble(v1); + const double b = value_toDouble(v2); + value_assign_double(dst, fmod(a, b) / b < 0.0 ? fmod(a, b) + b : fmod(a, b)); } /* comparison */ diff --git a/src/scratch/value_functions_p.h b/src/scratch/value_functions_p.h index ca497e99..05ec0b88 100644 --- a/src/scratch/value_functions_p.h +++ b/src/scratch/value_functions_p.h @@ -18,6 +18,19 @@ namespace libscratchcpp { +template +inline unsigned int value_intDigitCount(T v) +{ + unsigned int i = 0; + + while (v >= 1) { + v /= 10; + i++; + } + + return i; +} + template inline unsigned int value_digitCount(T v) { @@ -50,7 +63,7 @@ extern "C" return v < 0 && std::isinf(v); } - inline long value_convert_int_str(const char *str, int n, bool *ok) + inline double value_convert_int_str(const char *str, int n, bool *ok) { if (ok) *ok = false; @@ -80,7 +93,7 @@ extern "C" *ok = true; if (isNegative) - return -ret; + return -static_cast(ret); // for negative zero else return ret; } @@ -407,51 +420,6 @@ extern "C" } } -inline void value_doubleToString(double v, std::string *dst) -{ - if (v == 0) { - dst->assign("0"); - return; - } else if (std::isinf(v)) { - if (v > 0) - dst->assign("Infinity"); - else - dst->assign("-Infinity"); - - return; - } else if (std::isnan(v)) { - dst->assign("NaN"); - return; - } - - std::stringstream stream; - - if (v != 0) { - const int exponent = std::log10(std::abs(v)); - - if (exponent > 20) - stream << std::scientific << std::setprecision(value_digitCount(v / std::pow(10, exponent + 1)) - 1) << v; - else - stream << std::setprecision(std::max(16u, value_digitCount(v))) << v; - } else - stream << std::setprecision(std::max(16u, value_digitCount(v))) << v; - - dst->assign(stream.str()); - std::size_t index; - - for (int i = 0; i < 2; i++) { - if (i == 0) - index = dst->find("e+"); - else - index = dst->find("e-"); - - if (index != std::string::npos) { - while ((dst->size() >= index + 3) && ((*dst)[index + 2] == '0')) - dst->erase(index + 2, 1); - } - } -} - extern "C" { inline double value_floatToDouble(float v) diff --git a/test/assets/asset_test.cpp b/test/assets/asset_test.cpp index d08d56d6..5bd5920b 100644 --- a/test/assets/asset_test.cpp +++ b/test/assets/asset_test.cpp @@ -1,4 +1,5 @@ #include +#include #include "../common.h" #include "testasset.h" @@ -35,3 +36,13 @@ TEST(AssetTest, Data) ASSERT_EQ(asset.processedData, data); ASSERT_EQ(asset.callCount, 1); } + +TEST(AssetTest, Target) +{ + Asset asset("sound1", "a", "wav"); + ASSERT_EQ(asset.target(), nullptr); + + Target target; + asset.setTarget(&target); + ASSERT_EQ(asset.target(), &target); +} diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp index f36b3e6b..d3b81bf2 100644 --- a/test/assets/sound_test.cpp +++ b/test/assets/sound_test.cpp @@ -145,16 +145,6 @@ TEST_F(SoundTest, IsPlaying) SoundPrivate::audioOutput = nullptr; } -TEST_F(SoundTest, Target) -{ - Sound sound("sound1", "a", "wav"); - ASSERT_EQ(sound.target(), nullptr); - - Target target; - sound.setTarget(&target); - ASSERT_EQ(sound.target(), &target); -} - TEST_F(SoundTest, Clone) { auto sound = std::make_shared("sound1", "a", "wav"); diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp index ecb5588c..e486f372 100644 --- a/test/blocks/sensing_blocks_test.cpp +++ b/test/blocks/sensing_blocks_test.cpp @@ -422,21 +422,21 @@ TEST_F(SensingBlocksTest, TouchingColorImpl) vm.setFunctions(functions); vm.setConstValues(constValues); - EXPECT_CALL(target, touchingColor(constValues[0])).WillOnce(Return(false)); + EXPECT_CALL(target, touchingColor(constValues[0].toRgba())).WillOnce(Return(false)); vm.setBytecode(bytecode1); vm.run(); ASSERT_EQ(vm.registerCount(), 1); ASSERT_FALSE(vm.getInput(0, 1)->toBool()); - EXPECT_CALL(target, touchingColor(constValues[0])).WillOnce(Return(true)); + EXPECT_CALL(target, touchingColor(constValues[0].toRgba())).WillOnce(Return(true)); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 1); ASSERT_TRUE(vm.getInput(0, 1)->toBool()); - EXPECT_CALL(target, touchingColor(constValues[1])).WillOnce(Return(false)); + EXPECT_CALL(target, touchingColor(constValues[1].toRgba())).WillOnce(Return(false)); vm.reset(); vm.setBytecode(bytecode2); vm.run(); @@ -444,7 +444,7 @@ TEST_F(SensingBlocksTest, TouchingColorImpl) ASSERT_EQ(vm.registerCount(), 1); ASSERT_FALSE(vm.getInput(0, 1)->toBool()); - EXPECT_CALL(target, touchingColor(constValues[1])).WillOnce(Return(true)); + EXPECT_CALL(target, touchingColor(constValues[1].toRgba())).WillOnce(Return(true)); vm.reset(); vm.run(); @@ -495,21 +495,21 @@ TEST_F(SensingBlocksTest, ColorIsTouchingColorImpl) vm.setFunctions(functions); vm.setConstValues(constValues); - EXPECT_CALL(target, touchingColor(constValues[0], constValues[1])).WillOnce(Return(false)); + EXPECT_CALL(target, touchingColor(constValues[0].toRgba(), constValues[1].toRgba())).WillOnce(Return(false)); vm.setBytecode(bytecode1); vm.run(); ASSERT_EQ(vm.registerCount(), 1); ASSERT_FALSE(vm.getInput(0, 1)->toBool()); - EXPECT_CALL(target, touchingColor(constValues[0], constValues[1])).WillOnce(Return(true)); + EXPECT_CALL(target, touchingColor(constValues[0].toRgba(), constValues[1].toRgba())).WillOnce(Return(true)); vm.reset(); vm.run(); ASSERT_EQ(vm.registerCount(), 1); ASSERT_TRUE(vm.getInput(0, 1)->toBool()); - EXPECT_CALL(target, touchingColor(constValues[2], constValues[3])).WillOnce(Return(false)); + EXPECT_CALL(target, touchingColor(constValues[2].toRgba(), constValues[3].toRgba())).WillOnce(Return(false)); vm.reset(); vm.setBytecode(bytecode2); vm.run(); @@ -517,7 +517,7 @@ TEST_F(SensingBlocksTest, ColorIsTouchingColorImpl) ASSERT_EQ(vm.registerCount(), 1); ASSERT_FALSE(vm.getInput(0, 1)->toBool()); - EXPECT_CALL(target, touchingColor(constValues[2], constValues[3])).WillOnce(Return(true)); + EXPECT_CALL(target, touchingColor(constValues[2].toRgba(), constValues[3].toRgba())).WillOnce(Return(true)); vm.reset(); vm.run(); diff --git a/test/compiler/CMakeLists.txt b/test/compiler/CMakeLists.txt index 9dc335df..0b7d501a 100644 --- a/test/compiler/CMakeLists.txt +++ b/test/compiler/CMakeLists.txt @@ -9,6 +9,7 @@ target_link_libraries( compiler_test GTest::gtest_main scratchcpp + zip nlohmann_json::nlohmann_json ) diff --git a/test/compiler/testextension.cpp b/test/compiler/testextension.cpp index cb001061..ce26bbc3 100644 --- a/test/compiler/testextension.cpp +++ b/test/compiler/testextension.cpp @@ -18,6 +18,11 @@ std::string TestExtension::description() const return ""; } +Rgb TestExtension::color() const +{ + return rgb(0, 0, 0); +} + void TestExtension::registerBlocks(IEngine *engine) { engine->addInput(this, "INPUT1", INPUT1); diff --git a/test/compiler/testextension.h b/test/compiler/testextension.h index c7bdf102..6efc819d 100644 --- a/test/compiler/testextension.h +++ b/test/compiler/testextension.h @@ -25,6 +25,7 @@ class TestExtension : public IExtension std::string name() const override; std::string description() const override; + Rgb color() const override; void registerBlocks(IEngine *engine) override; diff --git a/test/dev/CMakeLists.txt b/test/dev/CMakeLists.txt index 79ee0e67..b9db18f7 100644 --- a/test/dev/CMakeLists.txt +++ b/test/dev/CMakeLists.txt @@ -2,3 +2,5 @@ add_subdirectory(blocks) add_subdirectory(executioncontext) add_subdirectory(llvm) add_subdirectory(compiler) +add_subdirectory(promise) +add_subdirectory(test_api) diff --git a/test/dev/blocks/CMakeLists.txt b/test/dev/blocks/CMakeLists.txt index 3e32a0d0..e8694da9 100644 --- a/test/dev/blocks/CMakeLists.txt +++ b/test/dev/blocks/CMakeLists.txt @@ -1,3 +1,15 @@ +add_library( + block_test_deps SHARED + util.cpp + util.h +) + +target_link_libraries( + block_test_deps + GTest::gtest_main + scratchcpp +) + # motion_blocks_test if (LIBSCRATCHCPP_ENABLE_MOTION_BLOCKS) add_executable( @@ -83,6 +95,7 @@ if (LIBSCRATCHCPP_ENABLE_CONTROL_BLOCKS) GTest::gmock_main scratchcpp scratchcpp_mocks + block_test_deps ) gtest_discover_tests(control_blocks_test) @@ -119,6 +132,7 @@ if (LIBSCRATCHCPP_ENABLE_OPERATOR_BLOCKS) GTest::gmock_main scratchcpp scratchcpp_mocks + block_test_deps ) gtest_discover_tests(operator_blocks_test) @@ -155,6 +169,7 @@ if (LIBSCRATCHCPP_ENABLE_LIST_BLOCKS) GTest::gmock_main scratchcpp scratchcpp_mocks + block_test_deps ) gtest_discover_tests(list_blocks_test) @@ -173,6 +188,7 @@ if (LIBSCRATCHCPP_ENABLE_CUSTOM_BLOCKS) GTest::gmock_main scratchcpp scratchcpp_mocks + block_test_deps ) gtest_discover_tests(custom_blocks_test) diff --git a/test/dev/blocks/control_blocks_test.cpp b/test/dev/blocks/control_blocks_test.cpp index b5cff150..9f054691 100644 --- a/test/dev/blocks/control_blocks_test.cpp +++ b/test/dev/blocks/control_blocks_test.cpp @@ -1,15 +1,1115 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include +#include #include "../common.h" #include "dev/blocks/controlblocks.h" +#include "util.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; + +using ::testing::Return; +using ::testing::SaveArg; +using ::testing::_; class ControlBlocksTest : 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; }; + +TEST_F(ControlBlocksTest, Forever) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_forever"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + testing::internal::CaptureStdout(); + m_engine->step(); + ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n"); + ASSERT_TRUE(m_engine->isRunning()); + } + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("control_forever"); + + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + } + } +} + +TEST_F(ControlBlocksTest, Repeat) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_repeat"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + builder.addValueInput("TIMES", 5); + + builder.addBlock("control_repeat"); + substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + builder.addNullObscuredInput("TIMES"); + + builder.build(); + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\ntest\ntest\ntest\ntest\n"); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("control_repeat"); + builder.addValueInput("TIMES", "Infinity"); + + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + } + } +} + +TEST_F(ControlBlocksTest, If) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_if"); + auto substack = std::make_shared("", "test_print_test"); + builder.addValueInput("CONDITION", false); + builder.addObscuredInput("SUBSTACK", substack); + + builder.addBlock("control_if"); + substack = std::make_shared("", "test_print_test"); + builder.addNullObscuredInput("CONDITION"); + builder.addObscuredInput("SUBSTACK", substack); + + builder.addBlock("control_if"); + substack = std::make_shared("", "test_print_test"); + builder.addValueInput("CONDITION", true); + builder.addObscuredInput("SUBSTACK", substack); + + builder.build(); + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n"); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("control_if"); + builder.addValueInput("CONDITION", true); + + builder.build(); + builder.run(); + } +} + +TEST_F(ControlBlocksTest, IfElse) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", false); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + auto substack2 = std::make_shared("", "test_print_test2"); + builder.addObscuredInput("SUBSTACK2", substack2); + + builder.addBlock("control_if_else"); + builder.addNullObscuredInput("CONDITION"); + substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + substack2 = std::make_shared("", "test_print_test2"); + builder.addObscuredInput("SUBSTACK2", substack2); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", true); + substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + substack2 = std::make_shared("", "test_print_test2"); + builder.addObscuredInput("SUBSTACK2", substack2); + + builder.build(); + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "test2\ntest2\ntest\n"); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", false); + auto substack2 = std::make_shared("", "test_print_test2"); + builder.addObscuredInput("SUBSTACK2", substack2); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", true); + substack2 = std::make_shared("", "test_print_test2"); + builder.addObscuredInput("SUBSTACK2", substack2); + + builder.build(); + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "test2\n"); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", false); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", true); + substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + + builder.build(); + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "test\n"); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", false); + + builder.addBlock("control_if_else"); + builder.addValueInput("CONDITION", true); + + builder.build(); + builder.run(); + } +} + +TEST_F(ControlBlocksTest, Stop) +{ + auto target = std::make_shared(); + + // Stop all + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_stop"); + builder.addDropdownField("STOP_OPTION", "all"); + auto block = builder.currentBlock(); + + 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); + + EXPECT_CALL(m_engineMock, stop()); + thread.run(); + } + + m_engine->clear(); + target = std::make_shared(); + + // Stop this script + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_stop"); + builder.addDropdownField("STOP_OPTION", "this script"); + builder.addBlock("test_print_test"); + + builder.build(); + + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_TRUE(testing::internal::GetCapturedStdout().empty()); + } + + m_engine->clear(); + target = std::make_shared(); + + // Stop other scripts in sprite + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_stop"); + builder.addDropdownField("STOP_OPTION", "other scripts in sprite"); + auto block = builder.currentBlock(); + + 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); + + EXPECT_CALL(m_engineMock, stopTarget(target.get(), &thread)); + thread.run(); + } + + // Stop other scripts in stage + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_stop"); + builder.addDropdownField("STOP_OPTION", "other scripts in stage"); + auto block = builder.currentBlock(); + + 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); + + EXPECT_CALL(m_engineMock, stopTarget(target.get(), &thread)); + thread.run(); + } +} + +TEST_F(ControlBlocksTest, Wait) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_wait"); + builder.addValueInput("DURATION", 2.5); + auto block = builder.currentBlock(); + + 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); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(2.5)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, requestRedraw()).Times(0); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(false)); + EXPECT_CALL(m_engineMock, requestRedraw()).Times(0); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw()).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_wait"); + builder.addNullObscuredInput("DURATION"); + auto block = builder.currentBlock(); + + 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); + StackTimerMock timer; + ctx->setStackTimer(&timer); + + EXPECT_CALL(timer, start(0.0)); + EXPECT_CALL(m_engineMock, requestRedraw()); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + EXPECT_CALL(timer, elapsed()).WillOnce(Return(true)); + EXPECT_CALL(m_engineMock, requestRedraw()).Times(0); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + } +} + +TEST_F(ControlBlocksTest, WaitUntil) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_wait_until"); + builder.addValueInput("CONDITION", false); + builder.build(); + m_engine->start(); + + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_wait_until"); + builder.addValueInput("CONDITION", true); + builder.build(); + m_engine->start(); + + m_engine->step(); + m_engine->step(); + ASSERT_FALSE(m_engine->isRunning()); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_wait_until"); + auto block = std::make_shared("", "test_condition"); + builder.addObscuredInput("CONDITION", block); + builder.build(); + + conditionReturnValue = false; + m_engine->start(); + + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + + conditionReturnValue = true; + m_engine->step(); + m_engine->step(); + ASSERT_FALSE(m_engine->isRunning()); + } +} + +TEST_F(ControlBlocksTest, RepeatUntil) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_repeat_until"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + builder.addValueInput("CONDITION", false); + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + testing::internal::CaptureStdout(); + m_engine->step(); + ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n"); + ASSERT_TRUE(m_engine->isRunning()); + } + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_repeat_until"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + builder.addValueInput("CONDITION", true); + builder.build(); + m_engine->start(); + + testing::internal::CaptureStdout(); + m_engine->step(); + m_engine->step(); + ASSERT_TRUE(testing::internal::GetCapturedStdout().empty()); + ASSERT_FALSE(m_engine->isRunning()); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_repeat_until"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + auto block = std::make_shared("", "test_condition"); + builder.addObscuredInput("CONDITION", block); + builder.build(); + + conditionReturnValue = false; + m_engine->start(); + + testing::internal::CaptureStdout(); + m_engine->step(); + ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n"); + ASSERT_TRUE(m_engine->isRunning()); + + conditionReturnValue = true; + m_engine->step(); + m_engine->step(); + ASSERT_FALSE(m_engine->isRunning()); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("control_repeat_until"); + builder.addValueInput("CONDITION", false); + + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + } + } +} + +TEST_F(ControlBlocksTest, While) +{ + auto target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_while"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + builder.addValueInput("CONDITION", true); + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + testing::internal::CaptureStdout(); + m_engine->step(); + ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n"); + ASSERT_TRUE(m_engine->isRunning()); + } + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_while"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + builder.addValueInput("CONDITION", false); + builder.build(); + m_engine->start(); + + testing::internal::CaptureStdout(); + m_engine->step(); + m_engine->step(); + ASSERT_TRUE(testing::internal::GetCapturedStdout().empty()); + ASSERT_FALSE(m_engine->isRunning()); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_while"); + auto substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + auto block = std::make_shared("", "test_condition"); + builder.addObscuredInput("CONDITION", block); + builder.build(); + + conditionReturnValue = true; + m_engine->start(); + + testing::internal::CaptureStdout(); + m_engine->step(); + ASSERT_EQ(testing::internal::GetCapturedStdout().substr(0, 10), "test\ntest\n"); + ASSERT_TRUE(m_engine->isRunning()); + + conditionReturnValue = false; + m_engine->step(); + m_engine->step(); + ASSERT_FALSE(m_engine->isRunning()); + } + + m_engine->clear(); + target = std::make_shared(); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("control_while"); + builder.addValueInput("CONDITION", true); + + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + } + } +} + +TEST_F(ControlBlocksTest, ForEach) +{ + auto target = std::make_shared(); + auto var1 = std::make_shared("", ""); + auto var2 = std::make_shared("", ""); + target->addVariable(var1); + target->addVariable(var2); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_for_each"); + + auto substack = std::make_shared("", "test_print"); + auto input = std::make_shared("STRING", Input::Type::ObscuredShadow); + input->primaryValue()->setValuePtr(var1); + substack->addInput(input); + + builder.addObscuredInput("SUBSTACK", substack); + + builder.addValueInput("VALUE", 5); + builder.addEntityField("VARIABLE", var1); + + builder.addBlock("control_for_each"); + substack = std::make_shared("", "test_print_test"); + builder.addObscuredInput("SUBSTACK", substack); + builder.addNullObscuredInput("VALUE"); + builder.addEntityField("VARIABLE", var2); + + builder.build(); + + var1->setValue(10); + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "1\n2\n3\n4\n5\n"); + ASSERT_EQ(var1->value(), 5); + } + + m_engine->clear(); + target = std::make_shared(); + target->addVariable(var1); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_for_each"); + + auto substack = std::make_shared("", "test_print"); + auto input = std::make_shared("STRING", Input::Type::ObscuredShadow); + input->primaryValue()->setValuePtr(var1); + substack->addInput(input); + + auto setVar = std::make_shared("", "test_set_var"); + substack->setNext(setVar); + setVar->setParent(substack); + auto field = std::make_shared("VARIABLE", ""); + setVar->addField(field); + input = std::make_shared("VALUE", Input::Type::Shadow); + input->setPrimaryValue(0); + setVar->addInput(input); + + auto printAgain = std::make_shared("", "test_print"); + setVar->setNext(printAgain); + printAgain->setParent(setVar); + input = std::make_shared("STRING", Input::Type::ObscuredShadow); + printAgain->addInput(input); + + builder.addObscuredInput("SUBSTACK", substack); + + builder.addValueInput("VALUE", 3); + builder.addEntityField("VARIABLE", var1); + + field->setValuePtr(var1); + input->primaryValue()->setValuePtr(var1); + + builder.build(); + + var1->setValue(7); + testing::internal::CaptureStdout(); + builder.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "1\n0\n2\n0\n3\n0\n"); + ASSERT_EQ(var1->value(), 0); + } + + m_engine->clear(); + target = std::make_shared(); + target->addVariable(var1); + + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + builder.addBlock("control_for_each"); + builder.addValueInput("VALUE", "Infinity"); + builder.addEntityField("VARIABLE", var1); + + builder.build(); + m_engine->start(); + + for (int i = 0; i < 2; i++) { + m_engine->step(); + ASSERT_TRUE(m_engine->isRunning()); + } + + ASSERT_GT(var1->value(), 0); + } +} + +TEST_F(ControlBlocksTest, StartAsClone) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_start_as_clone"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addCloneInitScript(block)); + compiler.compile(block); +} + +TEST_F(ControlBlocksTest, CreateCloneOfSprite) +{ + EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + auto target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of [Sprite1] + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + builder.addDropdownInput("CLONE_OPTION", "Sprite1"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("Sprite1")).WillOnce(Return(4)); + 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); + + Sprite sprite; + sprite.setEngine(&m_engineMock); + std::shared_ptr clone; + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite)); + EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone)); + EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite)); + thread.run(); + ASSERT_TRUE(clone); + ASSERT_EQ(clone->cloneSprite(), &sprite); + } + + m_engine->clear(); + target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of [myself] + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + builder.addDropdownInput("CLONE_OPTION", "_myself_"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + 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); + + std::shared_ptr clone; + EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone)); + EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, target.get())); + thread.run(); + ASSERT_TRUE(clone); + ASSERT_EQ(clone->cloneSprite(), target.get()); + } + + m_engine->clear(); + target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of ["_mYself_"] + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + builder.addDropdownInput("CLONE_OPTION", "_mYself_"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_mYself_")).WillOnce(Return(4)); + 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); + + Sprite sprite; + sprite.setEngine(&m_engineMock); + std::shared_ptr clone; + EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite)); + EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone)); + EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite)); + thread.run(); + ASSERT_TRUE(clone); + ASSERT_EQ(clone->cloneSprite(), &sprite); + } + + m_engine->clear(); + target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of (null block) + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + builder.addNullObscuredInput("CLONE_OPTION"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + 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); + + Sprite sprite; + sprite.setEngine(&m_engineMock); + std::shared_ptr clone; + EXPECT_CALL(m_engineMock, findTarget("0")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&sprite)); + EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone)); + EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite)); + thread.run(); + ASSERT_TRUE(clone); + ASSERT_EQ(clone->cloneSprite(), &sprite); + } + + m_engine->clear(); + target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of ("_myself_") + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + auto valueBlock = std::make_shared("", "test_input"); + auto input = std::make_shared("INPUT", Input::Type::Shadow); + input->setPrimaryValue("_myself_"); + valueBlock->addInput(input); + builder.addObscuredInput("CLONE_OPTION", valueBlock); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + 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); + + std::shared_ptr clone; + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone)); + EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, target.get())); + thread.run(); + ASSERT_TRUE(clone); + ASSERT_EQ(clone->cloneSprite(), target.get()); + } + + // create clone of ("_mYself_") + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + auto valueBlock = std::make_shared("", "test_input"); + auto input = std::make_shared("INPUT", Input::Type::Shadow); + input->setPrimaryValue("_mYself_"); + valueBlock->addInput(input); + builder.addObscuredInput("CLONE_OPTION", valueBlock); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + 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); + + Sprite sprite; + sprite.setEngine(&m_engineMock); + std::shared_ptr clone; + EXPECT_CALL(m_engineMock, findTarget("_mYself_")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&sprite)); + EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone)); + EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite)); + thread.run(); + ASSERT_TRUE(clone); + ASSERT_EQ(clone->cloneSprite(), &sprite); + } +} + +TEST_F(ControlBlocksTest, CreateCloneOfStage) +{ + EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1)); + EXPECT_CALL(m_engineMock, requestRedraw()).WillRepeatedly(Return()); + auto target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of [Stage] + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + builder.addDropdownInput("CLONE_OPTION", "_stage_"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget("_stage_")).WillOnce(Return(8)); + 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); + + Stage stage; + stage.setEngine(&m_engineMock); + EXPECT_CALL(m_engineMock, targetAt(8)).WillOnce(Return(&stage)); + EXPECT_CALL(m_engineMock, initClone).Times(0); + thread.run(); + } + + m_engine->clear(); + target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of [myself] + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + builder.addDropdownInput("CLONE_OPTION", "_myself_"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + 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); + + EXPECT_CALL(m_engineMock, initClone).Times(0); + thread.run(); + } + + m_engine->clear(); + target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of (null block) + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + builder.addNullObscuredInput("CLONE_OPTION"); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + 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); + + Stage stage; + stage.setEngine(&m_engineMock); + EXPECT_CALL(m_engineMock, findTarget("0")).WillOnce(Return(2)); + EXPECT_CALL(m_engineMock, targetAt(2)).WillOnce(Return(&stage)); + EXPECT_CALL(m_engineMock, initClone).Times(0); + thread.run(); + } + + m_engine->clear(); + target = std::make_shared(); + target->setEngine(&m_engineMock); + + // create clone of ("_myself_") + { + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_create_clone_of"); + auto valueBlock = std::make_shared("", "test_input"); + auto input = std::make_shared("INPUT", Input::Type::Shadow); + input->setPrimaryValue("_myself_"); + valueBlock->addInput(input); + builder.addObscuredInput("CLONE_OPTION", valueBlock); + auto block = builder.currentBlock(); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + 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); + + EXPECT_CALL(m_engineMock, findTarget).Times(0); + EXPECT_CALL(m_engineMock, initClone).Times(0); + thread.run(); + } +} + +TEST_F(ControlBlocksTest, DeleteThisClone) +{ + Sprite sprite; + sprite.setEngine(&m_engineMock); + + std::shared_ptr clone; + EXPECT_CALL(m_engineMock, cloneLimit()).WillRepeatedly(Return(-1)); + EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone)); + EXPECT_CALL(m_engineMock, moveDrawableBehindOther(_, &sprite)); + EXPECT_CALL(m_engineMock, requestRedraw()); + sprite.clone(); + ASSERT_TRUE(clone); + + ScriptBuilder builder(m_extension.get(), m_engine, clone); + + builder.addBlock("control_delete_this_clone"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, clone.get()); + auto code = compiler.compile(block); + Script script(clone.get(), block, &m_engineMock); + script.setCode(code); + Thread thread(clone.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, stopTarget(clone.get(), nullptr)); + EXPECT_CALL(m_engineMock, deinitClone(clone)); + thread.run(); +} + +TEST_F(ControlBlocksTest, DeleteThisCloneStage) +{ + auto target = std::make_shared(); + target->setEngine(&m_engineMock); + + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("control_delete_this_clone"); + auto block = builder.currentBlock(); + + 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); + + EXPECT_CALL(m_engineMock, stopTarget).Times(0); + EXPECT_CALL(m_engineMock, deinitClone).Times(0); + thread.run(); +} diff --git a/test/dev/blocks/custom_blocks_test.cpp b/test/dev/blocks/custom_blocks_test.cpp index a926f7db..515756f5 100644 --- a/test/dev/blocks/custom_blocks_test.cpp +++ b/test/dev/blocks/custom_blocks_test.cpp @@ -1,15 +1,103 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "../common.h" #include "dev/blocks/customblocks.h" +#include "util.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; class CustomBlocksTest : 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; }; + +TEST_F(CustomBlocksTest, Definition) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("procedures_definition"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + compiler.compile(block); + ASSERT_TRUE(compiler.unsupportedBlocks().empty()); +} + +TEST_F(CustomBlocksTest, CallWithArguments) +{ + const std::string procCode = "procedure %s %b"; + std::vector argumentIds = { "a", "b" }; + std::vector argumentNames = { "string or number", "boolean" }; + auto target = std::make_shared(); + + // Create definition + ScriptBuilder builder1(m_extension.get(), m_engine, target, false); + builder1.addBlock("procedures_prototype"); + auto prototypeBlock = builder1.takeBlock(); + BlockPrototype *prototype = prototypeBlock->mutationPrototype(); + prototype->setProcCode(procCode); + prototype->setArgumentIds(argumentIds); + prototype->setArgumentNames(argumentNames); + + builder1.addBlock("procedures_definition"); + builder1.addObscuredInput("custom_block", prototypeBlock); + builder1.currentBlock(); + + // Print first arg + builder1.addBlock("argument_reporter_string_number"); + builder1.addDropdownField("VALUE", "string or number"); + auto argBlock = builder1.takeBlock(); + + builder1.addBlock("test_print"); + builder1.addObscuredInput("STRING", argBlock); + + // Print second arg + builder1.addBlock("argument_reporter_boolean"); + builder1.addDropdownField("VALUE", "boolean"); + argBlock = builder1.takeBlock(); + + builder1.addBlock("test_print"); + builder1.addObscuredInput("STRING", argBlock); + + builder1.build(); + + // Call the procedure + ScriptBuilder builder2(m_extension.get(), m_engine, target); + auto block = std::make_shared("", "procedures_call"); + prototype = block->mutationPrototype(); + prototype->setProcCode(procCode); + prototype->setArgumentIds(argumentIds); + prototype->setArgumentNames(argumentNames); + builder2.addBlock(block); + builder2.addValueInput("a", "Hello world"); + builder2.addValueInput("b", true); + + ScriptBuilder::buildMultiple({ &builder1, &builder2 }); + + testing::internal::CaptureStdout(); + builder2.run(); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "Hello world\ntrue\n"); +} diff --git a/test/dev/blocks/event_blocks_test.cpp b/test/dev/blocks/event_blocks_test.cpp index ab8370a6..4c274e6d 100644 --- a/test/dev/blocks/event_blocks_test.cpp +++ b/test/dev/blocks/event_blocks_test.cpp @@ -1,15 +1,253 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include "../common.h" #include "dev/blocks/eventblocks.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; + +using ::testing::Return; class EventBlocksTest : 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); + } std::unique_ptr m_extension; + Project m_project; + IEngine *m_engine = nullptr; EngineMock m_engineMock; }; + +// TODO: Add test for when touching object hat predicate + +TEST_F(EventBlocksTest, WhenTouchingObject) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whentouchingobject"); + builder.addDropdownInput("TOUCHINGOBJECTMENU", "Sprite1"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addWhenTouchingObjectScript(block)); + compiler.compile(block); +} + +TEST_F(EventBlocksTest, WhenFlagClicked) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whenflagclicked"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addGreenFlagScript(block)); + compiler.compile(block); +} + +TEST_F(EventBlocksTest, WhenThisSpriteClicked) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whenthisspriteclicked"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addTargetClickScript(block)); + compiler.compile(block); +} + +TEST_F(EventBlocksTest, WhenStageClicked) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whenstageclicked"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addTargetClickScript(block)); + compiler.compile(block); +} + +TEST_F(EventBlocksTest, WhenBroadcastReceived) +{ + auto broadcast = std::make_shared("", ""); + m_engine->setBroadcasts({ broadcast }); + + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whenbroadcastreceived"); + builder.addEntityField("BROADCAST_OPTION", broadcast); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addBroadcastScript(block, block->fieldAt(0).get(), broadcast.get())); + compiler.compile(block); +} + +TEST_F(EventBlocksTest, WhenBackdropSwitchesTo) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whenbackdropswitchesto"); + builder.addDropdownField("BACKDROP", "backdrop2"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addBackdropChangeScript(block, block->fieldAt(0).get())); + compiler.compile(block); +} + +// TODO: Add test for when greater than hat predicate + +TEST_F(EventBlocksTest, WhenGreaterThan) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whengreaterthan"); + builder.addDropdownField("WHENGREATERTHANMENU", "LOUDNESS"); + builder.addValueInput("VALUE", 8.9); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addWhenGreaterThanScript(block)); + compiler.compile(block); +} + +TEST_F(EventBlocksTest, Broadcast) +{ + auto broadcast = std::make_shared("", "test"); + m_engine->setBroadcasts({ broadcast }); + + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_broadcast"); + builder.addEntityInput("BROADCAST_INPUT", "test", InputValue::Type::Broadcast, broadcast); + auto block1 = builder.currentBlock(); + + builder.addBlock("event_broadcast"); + builder.addNullObscuredInput("BROADCAST_INPUT"); + auto block2 = builder.currentBlock(); + + block1->setNext(nullptr); + block2->setParent(nullptr); + + { + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block1); + Script script(target.get(), block1, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 1, 4 }))); + EXPECT_CALL(m_engineMock, broadcast(1, &thread, false)); + EXPECT_CALL(m_engineMock, broadcast(4, &thread, false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); + ASSERT_FALSE(thread.promise()); + } + + { + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block2); + Script script(target.get(), block2, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findBroadcasts("0")).WillOnce(Return(std::vector({ 5, 7, 8 }))); + EXPECT_CALL(m_engineMock, broadcast(5, &thread, false)); + EXPECT_CALL(m_engineMock, broadcast(7, &thread, false)); + EXPECT_CALL(m_engineMock, broadcast(8, &thread, false)); + thread.run(); + ASSERT_TRUE(thread.isFinished()); + ASSERT_FALSE(thread.promise()); + } +} + +TEST_F(EventBlocksTest, BroadcastAndWait) +{ + auto broadcast = std::make_shared("", "test"); + m_engine->setBroadcasts({ broadcast }); + + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_broadcastandwait"); + builder.addEntityInput("BROADCAST_INPUT", "test", InputValue::Type::Broadcast, broadcast); + auto block1 = builder.currentBlock(); + + builder.addBlock("event_broadcastandwait"); + builder.addNullObscuredInput("BROADCAST_INPUT"); + auto block2 = builder.currentBlock(); + + block1->setNext(nullptr); + block2->setParent(nullptr); + + { + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block1); + Script script(target.get(), block1, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findBroadcasts("test")).WillOnce(Return(std::vector({ 1, 4 }))); + EXPECT_CALL(m_engineMock, broadcast(1, &thread, true)); + EXPECT_CALL(m_engineMock, broadcast(4, &thread, true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + ASSERT_TRUE(thread.promise()); + } + + { + Compiler compiler(&m_engineMock, target.get()); + auto code = compiler.compile(block2); + Script script(target.get(), block2, &m_engineMock); + script.setCode(code); + Thread thread(target.get(), &m_engineMock, &script); + + EXPECT_CALL(m_engineMock, findBroadcasts("0")).WillOnce(Return(std::vector({ 5, 7, 8 }))); + EXPECT_CALL(m_engineMock, broadcast(5, &thread, true)); + EXPECT_CALL(m_engineMock, broadcast(7, &thread, true)); + EXPECT_CALL(m_engineMock, broadcast(8, &thread, true)); + thread.run(); + ASSERT_FALSE(thread.isFinished()); + ASSERT_TRUE(thread.promise()); + } +} + +TEST_F(EventBlocksTest, WhenKeyPressed) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("event_whenkeypressed"); + builder.addDropdownField("KEY_OPTION", "a"); + auto block = builder.currentBlock(); + + Compiler compiler(&m_engineMock, target.get()); + EXPECT_CALL(m_engineMock, addKeyPressScript(block, block->fieldAt(0).get())); + compiler.compile(block); +} diff --git a/test/dev/blocks/list_blocks_test.cpp b/test/dev/blocks/list_blocks_test.cpp index 8f98a2fb..583f2a1f 100644 --- a/test/dev/blocks/list_blocks_test.cpp +++ b/test/dev/blocks/list_blocks_test.cpp @@ -1,15 +1,490 @@ +#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); + + 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); + + 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); + + 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); + + 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" + "0\n" + "0\n" + "0\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/operator_blocks_test.cpp b/test/dev/blocks/operator_blocks_test.cpp index f29aacec..c67bde95 100644 --- a/test/dev/blocks/operator_blocks_test.cpp +++ b/test/dev/blocks/operator_blocks_test.cpp @@ -1,15 +1,762 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include "../common.h" #include "dev/blocks/operatorblocks.h" +#include "util.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; + +using ::testing::Return; class OperatorBlocksTest : 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(OperatorBlocksTest, Add) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_add"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(Value(values[0]), 8.2); +} + +TEST_F(OperatorBlocksTest, Subtract) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_subtract"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(Value(values[0]), 3.2); +} + +TEST_F(OperatorBlocksTest, Multiply) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_multiply"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(Value(values[0]), 14.25); +} + +TEST_F(OperatorBlocksTest, Divide) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_divide"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 1); + ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 2.28); +} + +TEST_F(OperatorBlocksTest, Random) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + auto addRandomTest = [&builder](const Value &from, const Value &to) { + auto block = std::make_shared("", "operator_random"); + auto input = std::make_shared("FROM", Input::Type::Shadow); + input->setPrimaryValue(from); + block->addInput(input); + input = std::make_shared("TO", Input::Type::Shadow); + input->setPrimaryValue(to); + block->addInput(input); + + builder.addBlock("test_print"); + builder.addObscuredInput("STRING", block); + return builder.currentBlock(); + }; + + auto block = addRandomTest(-45, 12); + addRandomTest(12, 6.05); + addRandomTest(-78.686, -45); + addRandomTest(6.05, -78.686); + + 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 = + "-18\n" + "3.486789\n" + "-59.468873\n" + "-28.648764\n"; + + EXPECT_CALL(m_rng, randint(-45, 12)).WillOnce(Return(-18)); + EXPECT_CALL(m_rng, randintDouble(12, 6.05)).WillOnce(Return(3.486789)); + EXPECT_CALL(m_rng, randintDouble(-78.686, -45)).WillOnce(Return(-59.468873)); + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).WillOnce(Return(-28.648764)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(OperatorBlocksTest, Lt) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_lt"); + builder.addValueInput("OPERAND1", 5.4645); + builder.addValueInput("OPERAND2", 12.486); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_lt"); + builder.addValueInput("OPERAND1", 153.25); + builder.addValueInput("OPERAND2", 96.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_lt"); + builder.addValueInput("OPERAND1", 2.8465); + builder.addValueInput("OPERAND2", 2.8465); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), true); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), false); +} + +TEST_F(OperatorBlocksTest, Equals) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_equals"); + builder.addValueInput("OPERAND1", 5.4645); + builder.addValueInput("OPERAND2", 12.486); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_equals"); + builder.addValueInput("OPERAND1", 153.25); + builder.addValueInput("OPERAND2", 96.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_equals"); + builder.addValueInput("OPERAND1", 2.8465); + builder.addValueInput("OPERAND2", 2.8465); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), true); +} + +TEST_F(OperatorBlocksTest, Gt) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_gt"); + builder.addValueInput("OPERAND1", 5.4645); + builder.addValueInput("OPERAND2", 12.486); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_gt"); + builder.addValueInput("OPERAND1", 153.25); + builder.addValueInput("OPERAND2", 96.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_gt"); + builder.addValueInput("OPERAND1", 2.8465); + builder.addValueInput("OPERAND2", 2.8465); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), true); + ASSERT_EQ(Value(values[2]), false); +} + +TEST_F(OperatorBlocksTest, And) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_and"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 4); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), false); + ASSERT_EQ(Value(values[3]), true); +} + +TEST_F(OperatorBlocksTest, Or) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", false); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_or"); + builder.addValueInput("OPERAND1", true); + builder.addValueInput("OPERAND2", true); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 4); + ASSERT_EQ(Value(values[0]), false); + ASSERT_EQ(Value(values[1]), true); + ASSERT_EQ(Value(values[2]), true); + ASSERT_EQ(Value(values[3]), true); +} + +TEST_F(OperatorBlocksTest, Not) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_not"); + builder.addValueInput("OPERAND", false); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_not"); + builder.addValueInput("OPERAND", true); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(Value(values[0]), true); + ASSERT_EQ(Value(values[1]), false); +} + +TEST_F(OperatorBlocksTest, Join) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_join"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "def"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_join"); + builder.addValueInput("STRING1", "Hello "); + builder.addValueInput("STRING2", "world"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(Value(values[0]), "abcdef"); + ASSERT_EQ(Value(values[1]), "Hello world"); +} + +TEST_F(OperatorBlocksTest, LetterOf) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 2); + builder.addValueInput("STRING", "abc"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 7); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 0); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 12); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_letter_of"); + builder.addValueInput("LETTER", 1); + builder.addValueInput("STRING", "Ábč"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 5); + ASSERT_EQ(Value(values[0]), "b"); + ASSERT_EQ(Value(values[1]), "w"); + ASSERT_EQ(Value(values[2]), ""); + ASSERT_EQ(Value(values[3]), ""); + ASSERT_EQ(Value(values[4]), "Á"); +} + +TEST_F(OperatorBlocksTest, Length) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_length"); + builder.addValueInput("STRING", "abc"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_length"); + builder.addValueInput("STRING", "Hello world"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_length"); + builder.addValueInput("STRING", "dOádčĐaší"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 3); + ASSERT_EQ(Value(values[0]), 3); + ASSERT_EQ(Value(values[1]), 11); + ASSERT_EQ(Value(values[2]), 9); +} + +TEST_F(OperatorBlocksTest, Contains) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "a"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "e"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "abc"); + builder.addValueInput("STRING2", "C"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "Hello world"); + builder.addValueInput("STRING2", "ello"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "Hello world"); + builder.addValueInput("STRING2", "olld"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "ábČ"); + builder.addValueInput("STRING2", "á"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "ábČ"); + builder.addValueInput("STRING2", "bČ"); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_contains"); + builder.addValueInput("STRING1", "ábČ"); + builder.addValueInput("STRING2", "ďá"); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 8); + ASSERT_EQ(Value(values[0]), true); + ASSERT_EQ(Value(values[1]), false); + ASSERT_EQ(Value(values[2]), true); + ASSERT_EQ(Value(values[3]), true); + ASSERT_EQ(Value(values[4]), false); + ASSERT_EQ(Value(values[5]), true); + ASSERT_EQ(Value(values[6]), true); + ASSERT_EQ(Value(values[7]), false); +} + +TEST_F(OperatorBlocksTest, Mod) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_mod"); + builder.addValueInput("NUM1", 5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mod"); + builder.addValueInput("NUM1", -5.7); + builder.addValueInput("NUM2", 2.5); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 0.7); + ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 1.8); +} + +TEST_F(OperatorBlocksTest, Round) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("operator_round"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_round"); + builder.addValueInput("NUM", 2.3); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(Value(values[0]), 6); + ASSERT_EQ(Value(values[1]), 2); +} + +TEST_F(OperatorBlocksTest, MathOp) +{ + auto target = std::make_shared(); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + // abs + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "abs"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "abs"); + builder.addValueInput("NUM", -5.7); + builder.captureBlockReturnValue(); + + // floor + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "floor"); + builder.addValueInput("NUM", 3.2); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "floor"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + // ceiling + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ceiling"); + builder.addValueInput("NUM", 3.2); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ceiling"); + builder.addValueInput("NUM", 5.7); + builder.captureBlockReturnValue(); + + // sqrt + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sqrt"); + builder.addValueInput("NUM", 16); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sqrt"); + builder.addValueInput("NUM", 2); + builder.captureBlockReturnValue(); + + // sin + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sin"); + builder.addValueInput("NUM", 90); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "sin"); + builder.addValueInput("NUM", 30); + builder.captureBlockReturnValue(); + + // cos + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "cos"); + builder.addValueInput("NUM", 0); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "cos"); + builder.addValueInput("NUM", 60); + builder.captureBlockReturnValue(); + + // tan + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "tan"); + builder.addValueInput("NUM", 30); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "tan"); + builder.addValueInput("NUM", 45); + builder.captureBlockReturnValue(); + + // asin + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "asin"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "asin"); + builder.addValueInput("NUM", 0.5); + builder.captureBlockReturnValue(); + + // acos + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "acos"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "acos"); + builder.addValueInput("NUM", 0.5); + builder.captureBlockReturnValue(); + + // atan + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "atan"); + builder.addValueInput("NUM", 0.5); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "atan"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + // ln + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ln"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "ln"); + builder.addValueInput("NUM", 10); + builder.captureBlockReturnValue(); + + // log + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "log"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "log"); + builder.addValueInput("NUM", 100); + builder.captureBlockReturnValue(); + + // e ^ + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "e ^"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "e ^"); + builder.addValueInput("NUM", 8.2); + builder.captureBlockReturnValue(); + + // 10 ^ + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "10 ^"); + builder.addValueInput("NUM", 1); + builder.captureBlockReturnValue(); + + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "10 ^"); + builder.addValueInput("NUM", 8.2); + builder.captureBlockReturnValue(); + + // invalid + builder.addBlock("operator_mathop"); + builder.addDropdownField("OPERATOR", "invalid"); + builder.addValueInput("NUM", -5.54); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 29); + ASSERT_EQ(std::round(value_toDouble(&values[0]) * 100) / 100, 5.7); + ASSERT_EQ(std::round(value_toDouble(&values[1]) * 100) / 100, 5.7); + ASSERT_EQ(std::round(value_toDouble(&values[2]) * 100) / 100, 3); + ASSERT_EQ(std::round(value_toDouble(&values[3]) * 100) / 100, 5); + ASSERT_EQ(std::round(value_toDouble(&values[4]) * 100) / 100, 4); + ASSERT_EQ(std::round(value_toDouble(&values[5]) * 100) / 100, 6); + ASSERT_EQ(std::round(value_toDouble(&values[6]) * 100) / 100, 4); + ASSERT_EQ(std::round(value_toDouble(&values[7]) * 100) / 100, 1.41); + ASSERT_EQ(std::round(value_toDouble(&values[8]) * 100) / 100, 1); + ASSERT_EQ(std::round(value_toDouble(&values[9]) * 100) / 100, 0.5); + ASSERT_EQ(std::round(value_toDouble(&values[10]) * 100) / 100, 1); + ASSERT_EQ(std::round(value_toDouble(&values[11]) * 100) / 100, 0.5); + ASSERT_EQ(std::round(value_toDouble(&values[12]) * 100) / 100, 0.58); + ASSERT_EQ(std::round(value_toDouble(&values[13]) * 100) / 100, 1); + ASSERT_EQ(std::round(value_toDouble(&values[14]) * 100) / 100, 90); + ASSERT_EQ(std::round(value_toDouble(&values[15]) * 100) / 100, 30); + ASSERT_EQ(std::round(value_toDouble(&values[16]) * 100) / 100, 0); + ASSERT_EQ(std::round(value_toDouble(&values[17]) * 100) / 100, 60); + ASSERT_EQ(std::round(value_toDouble(&values[18]) * 100) / 100, 26.57); + ASSERT_EQ(std::round(value_toDouble(&values[19]) * 100) / 100, 45); + ASSERT_EQ(std::round(value_toDouble(&values[20]) * 100) / 100, 0); + ASSERT_EQ(std::round(value_toDouble(&values[21]) * 100) / 100, 2.3); + ASSERT_EQ(std::round(value_toDouble(&values[22]) * 100) / 100, 0); + ASSERT_EQ(std::round(value_toDouble(&values[23]) * 100) / 100, 2); + ASSERT_EQ(std::round(value_toDouble(&values[24]) * 100) / 100, 2.72); + ASSERT_EQ(std::round(value_toDouble(&values[25]) * 100) / 100, 3640.95); + ASSERT_EQ(std::round(value_toDouble(&values[26]) * 100) / 100, 10); + ASSERT_EQ(std::round(value_toDouble(&values[27]) * 100) / 100, 158489319.25); + ASSERT_EQ(std::round(value_toDouble(&values[28]) * 100) / 100, 0); +} diff --git a/test/dev/blocks/util.cpp b/test/dev/blocks/util.cpp new file mode 100644 index 00000000..c81d9c7c --- /dev/null +++ b/test/dev/blocks/util.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +namespace libscratchcpp +{ + +void registerBlocks(IEngine *engine, IExtension *extension) +{ + engine->addCompileFunction(extension, "test_print", [](Compiler *compiler) -> CompilerValue * { + auto input = compiler->addInput("STRING"); + compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input }); + return nullptr; + }); + + engine->addCompileFunction(extension, "test_print_test", [](Compiler *compiler) -> CompilerValue * { + auto input = compiler->addConstValue("test"); + compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input }); + return nullptr; + }); + + engine->addCompileFunction(extension, "test_print_test2", [](Compiler *compiler) -> CompilerValue * { + auto input = compiler->addConstValue("test2"); + compiler->addFunctionCall("test_print", Compiler::StaticType::Void, { Compiler::StaticType::String }, { input }); + return nullptr; + }); + + engine->addCompileFunction(extension, "test_condition", [](Compiler *compiler) -> CompilerValue * { return compiler->addFunctionCall("test_condition", Compiler::StaticType::Bool); }); + + 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")); + return nullptr; + }); +} + +extern "C" void test_print(const char *str) +{ + std::cout << str << std::endl; +} + +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/blocks/util.h b/test/dev/blocks/util.h new file mode 100644 index 00000000..9fc7e76f --- /dev/null +++ b/test/dev/blocks/util.h @@ -0,0 +1,13 @@ +#pragma once + +namespace libscratchcpp +{ + +class IEngine; +class IExtension; + +bool conditionReturnValue = false; + +void registerBlocks(IEngine *engine, IExtension *extension); + +} // namespace libscratchcpp diff --git a/test/dev/blocks/variable_blocks_test.cpp b/test/dev/blocks/variable_blocks_test.cpp index 0a480f33..2789f289 100644 --- a/test/dev/blocks/variable_blocks_test.cpp +++ b/test/dev/blocks/variable_blocks_test.cpp @@ -1,15 +1,111 @@ +#include +#include +#include +#include +#include +#include #include #include "../common.h" #include "dev/blocks/variableblocks.h" using namespace libscratchcpp; +using namespace libscratchcpp::test; class VariableBlocksTest : 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); + } std::unique_ptr m_extension; + Project m_project; + IEngine *m_engine = nullptr; EngineMock m_engineMock; }; + +TEST_F(VariableBlocksTest, Variable) +{ + auto target = std::make_shared(); + auto var1 = std::make_shared("", "", 835.21); + target->addVariable(var1); + auto var2 = std::make_shared("", "", "Hello world"); + target->addVariable(var2); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("data_variable"); + builder.addEntityField("VARIABLE", var1); + builder.captureBlockReturnValue(); + + builder.addBlock("data_variable"); + builder.addEntityField("VARIABLE", var2); + builder.captureBlockReturnValue(); + + builder.build(); + builder.run(); + + List *valueList = builder.capturedValues(); + ValueData *values = valueList->data(); + ASSERT_EQ(valueList->size(), 2); + ASSERT_EQ(Value(values[0]), 835.21); + ASSERT_EQ(Value(values[1]), "Hello world"); +} + +TEST_F(VariableBlocksTest, SetVariableTo) +{ + auto target = std::make_shared(); + auto var1 = std::make_shared("", "", 835.21); + target->addVariable(var1); + auto var2 = std::make_shared("", "", "Hello world"); + target->addVariable(var2); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("data_setvariableto"); + builder.addEntityField("VARIABLE", var1); + builder.addValueInput("VALUE", "test"); + + builder.addBlock("data_setvariableto"); + builder.addEntityField("VARIABLE", var2); + builder.addValueInput("VALUE", 123); + + builder.build(); + builder.run(); + ASSERT_EQ(var1->value(), "test"); + ASSERT_EQ(var2->value(), 123); +} + +TEST_F(VariableBlocksTest, ChangeVariableBy) +{ + auto target = std::make_shared(); + auto var1 = std::make_shared("", "", 835.21); + target->addVariable(var1); + auto var2 = std::make_shared("", "", "Hello world"); + target->addVariable(var2); + ScriptBuilder builder(m_extension.get(), m_engine, target); + + builder.addBlock("data_changevariableby"); + builder.addEntityField("VARIABLE", var1); + builder.addValueInput("VALUE", "5.12"); + + builder.addBlock("data_changevariableby"); + builder.addEntityField("VARIABLE", var2); + builder.addValueInput("VALUE", -2.5); + + builder.build(); + + builder.run(); + ASSERT_EQ(var1->value(), 840.33); + ASSERT_EQ(var2->value(), -2.5); + + builder.run(); + ASSERT_EQ(var1->value(), 845.45); + ASSERT_EQ(var2->value(), -5); + + builder.run(); + ASSERT_EQ(var1->value(), 850.57); + ASSERT_EQ(var2->value(), -7.5); +} diff --git a/test/dev/compiler/CMakeLists.txt b/test/dev/compiler/CMakeLists.txt index 546ac5c6..639bbbb0 100644 --- a/test/dev/compiler/CMakeLists.txt +++ b/test/dev/compiler/CMakeLists.txt @@ -1,6 +1,10 @@ add_executable( compiler_test compiler_test.cpp + compilercontext_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 0ebe69c5..219a608b 100644 --- a/test/dev/compiler/compiler_test.cpp +++ b/test/dev/compiler/compiler_test.cpp @@ -1,4 +1,7 @@ #include +#include +#include +#include #include #include #include @@ -11,6 +14,7 @@ #include #include #include +#include #include "../common.h" @@ -24,7 +28,12 @@ class CompilerTest : public testing::Test public: void SetUp() override { + m_ctx = std::make_shared(&m_engine, &m_target); CompilerPrivate::builderFactory = &m_builderFactory; + + EXPECT_CALL(m_builderFactory, createCtx(&m_engine, &m_target)).WillOnce(Return(m_ctx)); + m_compiler = std::make_unique(&m_engine, &m_target); + m_builder = std::make_shared(); m_code = std::make_shared(); } @@ -37,18 +46,19 @@ class CompilerTest : public testing::Test m_testVar.reset(); } - void compile(Compiler &compiler, std::shared_ptr block) + void compile(Compiler *compiler, std::shared_ptr block, BlockPrototype *procedurePrototype = nullptr) { - ASSERT_EQ(compiler.block(), nullptr); - // TODO: Test warp - EXPECT_CALL(m_builderFactory, create(block->id(), false)).WillOnce(Return(m_builder)); + ASSERT_EQ(compiler->block(), nullptr); + EXPECT_CALL(m_builderFactory, create(m_ctx.get(), procedurePrototype)).WillOnce(Return(m_builder)); EXPECT_CALL(*m_builder, finalize()).WillOnce(Return(m_code)); - ASSERT_EQ(compiler.compile(block), m_code); - ASSERT_EQ(compiler.block(), nullptr); + ASSERT_EQ(compiler->compile(block), m_code); + ASSERT_EQ(compiler->block(), nullptr); } EngineMock m_engine; TargetMock m_target; + std::unique_ptr m_compiler; + std::shared_ptr m_ctx; CodeBuilderFactoryMock m_builderFactory; static inline std::shared_ptr m_builder; std::shared_ptr m_code; @@ -56,91 +66,306 @@ class CompilerTest : public testing::Test static inline std::shared_ptr m_testVar; }; -TEST_F(CompilerTest, Constructors) +TEST_F(CompilerTest, Engine) { - Compiler compiler(&m_engine, &m_target); - ASSERT_EQ(compiler.engine(), &m_engine); - ASSERT_EQ(compiler.target(), &m_target); + ASSERT_EQ(m_compiler->engine(), &m_engine); +} + +TEST_F(CompilerTest, Target) +{ + ASSERT_EQ(m_compiler->target(), &m_target); } TEST_F(CompilerTest, AddFunctionCall) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); m_compareBlock = block; - block->setCompileFunction([](Compiler *compiler) { - ASSERT_EQ(compiler->block(), m_compareBlock); - std::vector args = { Compiler::StaticType::Number, Compiler::StaticType::Bool }; - EXPECT_CALL(*m_builder, addFunctionCall("test1", Compiler::StaticType::Void, args)); - compiler->addFunctionCall("test1", Compiler::StaticType::Void, args); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_EQ(compiler->block(), m_compareBlock); + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + Compiler::ArgTypes argTypes = { Compiler::StaticType::Number, Compiler::StaticType::Bool }; + Compiler::Args args = { &arg1, &arg2 }; + EXPECT_CALL(*m_builder, addFunctionCall("test1", Compiler::StaticType::Void, argTypes, args)); + compiler->addFunctionCall("test1", Compiler::StaticType::Void, argTypes, args); + + args = { &arg1 }; + argTypes = { Compiler::StaticType::String }; + EXPECT_CALL(*m_builder, addFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args)); + compiler->addFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} - args = { Compiler::StaticType::String }; - EXPECT_CALL(*m_builder, addFunctionCall("test2", Compiler::StaticType::Bool, args)); - compiler->addFunctionCall("test2", Compiler::StaticType::Bool, args); +TEST_F(CompilerTest, AddTargetFunctionCall) +{ + + auto block = std::make_shared("a", ""); + m_compareBlock = block; + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_EQ(compiler->block(), m_compareBlock); + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + Compiler::ArgTypes argTypes = { Compiler::StaticType::Number, Compiler::StaticType::Bool }; + Compiler::Args args = { &arg1, &arg2 }; + EXPECT_CALL(*m_builder, addTargetFunctionCall("test1", Compiler::StaticType::Void, argTypes, args)); + compiler->addTargetFunctionCall("test1", Compiler::StaticType::Void, argTypes, args); + + args = { &arg1 }; + argTypes = { Compiler::StaticType::String }; + EXPECT_CALL(*m_builder, addTargetFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args)); + compiler->addTargetFunctionCall("test2", Compiler::StaticType::Bool, argTypes, args); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddFunctionCallWithCtx) +{ + + auto block = std::make_shared("a", ""); + m_compareBlock = block; + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_EQ(compiler->block(), m_compareBlock); + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + Compiler::ArgTypes argTypes = { Compiler::StaticType::Number, Compiler::StaticType::Bool }; + Compiler::Args args = { &arg1, &arg2 }; + EXPECT_CALL(*m_builder, addFunctionCallWithCtx("test1", Compiler::StaticType::Void, argTypes, args)); + compiler->addFunctionCallWithCtx("test1", Compiler::StaticType::Void, argTypes, args); + + args = { &arg1 }; + argTypes = { Compiler::StaticType::String }; + EXPECT_CALL(*m_builder, addFunctionCallWithCtx("test2", Compiler::StaticType::Bool, argTypes, args)); + compiler->addFunctionCallWithCtx("test2", Compiler::StaticType::Bool, argTypes, args); + + return nullptr; + }); + + compile(m_compiler.get(), block); } TEST_F(CompilerTest, AddConstValue) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, addConstValue(Value(1))); - compiler->addConstValue(1); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerConstant ret(Compiler::StaticType::Unknown, Value()); - EXPECT_CALL(*m_builder, addConstValue(Value("test"))); + EXPECT_CALL(*m_builder, addConstValue(Value(1))).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addConstValue(1), &ret); + + EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&ret)); compiler->addConstValue("test"); - EXPECT_CALL(*m_builder, addConstValue(Value(3.5))); - compiler->addConstValue(3.5); + EXPECT_CALL(*m_builder, addConstValue(Value(3.5))).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addConstValue(3.5), nullptr); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddLoopIndex) +{ + + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); + + EXPECT_CALL(*m_builder, addLoopIndex()).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addLoopIndex(), &ret); + + EXPECT_CALL(*m_builder, addLoopIndex()).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addLoopIndex(), nullptr); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddLocalVariableValue) +{ + + 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(m_compiler.get(), block); } TEST_F(CompilerTest, AddVariableValue) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); - block->setCompileFunction([](Compiler *compiler) { + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); Variable var1("", ""), var2("", ""); - EXPECT_CALL(*m_builder, addVariableValue(&var1)); - compiler->addVariableValue(&var1); - EXPECT_CALL(*m_builder, addVariableValue(&var2)); - compiler->addVariableValue(&var2); + EXPECT_CALL(*m_builder, addVariableValue(&var1)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addVariableValue(&var1), &ret); + + EXPECT_CALL(*m_builder, addVariableValue(&var2)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addVariableValue(&var2), nullptr); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } -TEST_F(CompilerTest, AddListValue) +TEST_F(CompilerTest, AddListContents) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); - block->setCompileFunction([](Compiler *compiler) { + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); List list1("", ""), list2("", ""); - EXPECT_CALL(*m_builder, addListContents(&list1)); - compiler->addListContents(&list1); - EXPECT_CALL(*m_builder, addListContents(&list2)); - compiler->addListContents(&list2); + EXPECT_CALL(*m_builder, addListContents(&list1)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addListContents(&list1), &ret); + + EXPECT_CALL(*m_builder, addListContents(&list2)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addListContents(&list2), nullptr); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddListItem) +{ + + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); + CompilerValue arg(Compiler::StaticType::Unknown); + List list1("", ""), list2("", ""); + + EXPECT_CALL(*m_builder, addListItem(&list1, &arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addListItem(&list1, &arg), &ret); + + EXPECT_CALL(*m_builder, addListItem(&list2, &arg)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addListItem(&list2, &arg), nullptr); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddListItemIndex) +{ + + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); + CompilerValue arg(Compiler::StaticType::Unknown); + List list1("", ""), list2("", ""); + + EXPECT_CALL(*m_builder, addListItemIndex(&list1, &arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addListItemIndex(&list1, &arg), &ret); + + EXPECT_CALL(*m_builder, addListItemIndex(&list2, &arg)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addListItemIndex(&list2, &arg), nullptr); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddListSize) +{ + + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); + List list1("", ""), list2("", ""); + + EXPECT_CALL(*m_builder, addListSize(&list1)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addListSize(&list1), &ret); + + EXPECT_CALL(*m_builder, addListSize(&list2)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addListSize(&list2), nullptr); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddListContains) +{ + + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); + CompilerValue arg(Compiler::StaticType::Unknown); + List list1("", ""), list2("", ""); + + EXPECT_CALL(*m_builder, addListContains(&list1, &arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addListContains(&list1, &arg), &ret); + + EXPECT_CALL(*m_builder, addListContains(&list2, &arg)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addListContains(&list2, &arg), nullptr); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, AddProcedureArgument) +{ + + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue ret(Compiler::StaticType::Unknown); + + EXPECT_CALL(*m_builder, addProcedureArgument("arg 1")).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addProcedureArgument("arg 1"), &ret); + + EXPECT_CALL(*m_builder, addProcedureArgument("arg 2")).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->addProcedureArgument("arg 2"), nullptr); + + return nullptr; + }); + + compile(m_compiler.get(), block); } TEST_F(CompilerTest, AddInput) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); auto valueBlock = std::make_shared("b", ""); m_compareBlock = valueBlock; - valueBlock->setCompileFunction([](Compiler *compiler) { - ASSERT_EQ(compiler->block(), m_compareBlock); - compiler->addConstValue("value block"); + valueBlock->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_EQ(compiler->block(), m_compareBlock); + return compiler->addConstValue("value block"); }); auto menu = std::make_shared("c", ""); @@ -184,235 +409,779 @@ TEST_F(CompilerTest, AddInput) obscuredShadowBlock->setValueBlock(valueBlock); block->addInput(obscuredShadowBlock); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, addConstValue(Value())); - compiler->addInput("INVALID"); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerConstant constRet(Compiler::StaticType::Unknown, Value()); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, addConstValue(Value())).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("INVALID"), &constRet); - EXPECT_CALL(*m_builder, addConstValue(Value("test"))); - compiler->addInput("SHADOW"); + EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("SHADOW"), &constRet); - EXPECT_CALL(*m_builder, addConstValue(Value("value block"))); - compiler->addInput("SHADOW_BLOCK"); + EXPECT_CALL(*m_builder, addConstValue(Value("value block"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("SHADOW_BLOCK"), &constRet); - EXPECT_CALL(*m_builder, addConstValue(Value("selected item"))); - compiler->addInput("SHADOW_MENU"); + EXPECT_CALL(*m_builder, addConstValue(Value("selected item"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("SHADOW_MENU"), &constRet); - EXPECT_CALL(*m_builder, addConstValue(Value("test"))); - compiler->addInput("NO_SHADOW"); + EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("NO_SHADOW"), &constRet); - EXPECT_CALL(*m_builder, addConstValue(Value("value block"))); - compiler->addInput("NO_SHADOW_BLOCK"); + EXPECT_CALL(*m_builder, addConstValue(Value("value block"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("NO_SHADOW_BLOCK"), &constRet); - EXPECT_CALL(*m_builder, addConstValue(Value("selected item"))); - compiler->addInput("NO_SHADOW_MENU"); + EXPECT_CALL(*m_builder, addConstValue(Value("selected item"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("NO_SHADOW_MENU"), &constRet); - EXPECT_CALL(*m_builder, addConstValue(Value("test"))); - compiler->addInput("OBSCURED_SHADOW"); + EXPECT_CALL(*m_builder, addConstValue(Value("test"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput("OBSCURED_SHADOW"), &constRet); - EXPECT_CALL(*m_builder, addVariableValue(m_testVar.get())); - compiler->addInput("OBSCURED_SHADOW_VARIABLE"); + EXPECT_CALL(*m_builder, addVariableValue(m_testVar.get())).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->addInput(compiler->input("OBSCURED_SHADOW_VARIABLE")), &ret); - EXPECT_CALL(*m_builder, addConstValue(Value("value block"))); - compiler->addInput("OBSCURED_SHADOW_BLOCK"); + EXPECT_CALL(*m_builder, addConstValue(Value("value block"))).WillOnce(Return(&constRet)); + EXPECT_EQ(compiler->addInput(compiler->input("OBSCURED_SHADOW_BLOCK")), &constRet); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateAdd) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createAdd); - compiler->createAdd(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createAdd(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createAdd(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateSub) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createSub); - compiler->createSub(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createSub(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createSub(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateMul) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createMul); - compiler->createMul(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createMul(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createMul(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateDiv) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createDiv); - compiler->createDiv(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createDiv(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createDiv(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateRandom) +{ + + 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, createRandom(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createRandom(&arg1, &arg2), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateRandomInt) +{ + + 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(m_compiler.get(), block); } TEST_F(CompilerTest, CreateCmpEQ) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createCmpEQ); - compiler->createCmpEQ(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createCmpEQ(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createCmpEQ(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateCmpGT) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createCmpGT); - compiler->createCmpGT(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createCmpGT(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createCmpGT(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateCmpLT) { - 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, createCmpLT(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createCmpLT(&arg1, &arg2), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateStrCmpEQ) +{ + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createCmpLT); - compiler->createCmpLT(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createStrCmpEQ(&arg1, &arg2, true)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createStrCmpEQ(&arg1, &arg2, true), &ret); + + EXPECT_CALL(*m_builder, createStrCmpEQ(&arg1, &arg2, false)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createStrCmpEQ(&arg1, &arg2, false), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateAnd) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createAnd); - compiler->createAnd(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createAnd(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createAnd(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateOr) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createOr); - compiler->createOr(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createOr(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createOr(&arg1, &arg2), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, CreateNot) { - Compiler compiler(&m_engine, &m_target); + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createNot(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createNot(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateMod) +{ + + 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, createMod(&arg1, &arg2)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createMod(&arg1, &arg2), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateRound) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createRound(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createRound(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateAbs) +{ + auto block = std::make_shared("", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, createNot); - compiler->createNot(); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createAbs(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createAbs(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateFloor) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createFloor(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createFloor(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateCeil) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createCeil(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createCeil(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateSqrt) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createSqrt(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createSqrt(&arg), &ret); + + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateSin) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createSin(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createSin(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateCos) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createCos(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createCos(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateTan) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createTan(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createTan(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateAsin) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createAsin(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createAsin(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateAcos) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createAcos(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createAcos(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateAtan) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createAtan(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createAtan(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateLn) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createLn(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createLn(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateLog10) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createLog10(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createLog10(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateExp) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createExp(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createExp(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateExp10) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createExp10(&arg)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createExp10(&arg), &ret); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateSelect) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + CompilerValue arg3(Compiler::StaticType::Unknown); + CompilerValue ret(Compiler::StaticType::Unknown); + + EXPECT_CALL(*m_builder, createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Number)).WillOnce(Return(&ret)); + EXPECT_EQ(compiler->createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Number), &ret); + + EXPECT_CALL(*m_builder, createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Unknown)).WillOnce(Return(nullptr)); + EXPECT_EQ(compiler->createSelect(&arg1, &arg2, &arg3, Compiler::StaticType::Unknown), nullptr); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateLocalVariable) +{ + + 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(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateLocalVariableWrite) +{ + + 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(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateVariableWrite) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + auto var = std::make_shared("", ""); + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, createVariableWrite(var.get(), &arg)); + compiler->createVariableWrite(var.get(), &arg); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CustomIfStatement) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginIfStatement(&arg)); + compiler->beginIfStatement(&arg); + EXPECT_CALL(*m_builder, endIf()); + compiler->endIf(); + + EXPECT_CALL(*m_builder, beginIfStatement(&arg)); + compiler->beginIfStatement(&arg); + EXPECT_CALL(*m_builder, beginElseBranch()); + compiler->beginElseBranch(); + EXPECT_CALL(*m_builder, endIf()); + compiler->endIf(); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CustomWhileLoop) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginWhileLoop(&arg)); + compiler->beginWhileLoop(&arg); + EXPECT_CALL(*m_builder, endLoop()); + compiler->endLoop(); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CustomRepeatUntilLoop) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg)); + compiler->beginRepeatUntilLoop(&arg); + EXPECT_CALL(*m_builder, endLoop()); + compiler->endLoop(); + + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, BeginLoopCondition) +{ + + auto block = std::make_shared("a", ""); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_CALL(*m_builder, beginLoopCondition()); + compiler->beginLoopCondition(); + return nullptr; + }); + + compile(m_compiler.get(), 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) { + if1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); EXPECT_CALL(*m_builder, beginIfStatement).Times(0); EXPECT_CALL(*m_builder, endIf).Times(0); - compiler->moveToIf(nullptr); + compiler->moveToIf(&arg, nullptr); + return nullptr; }); auto if2 = std::make_shared("", "if"); if1->setNext(if2); if2->setParent(if1); - if2->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginIfStatement()); + if2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginIfStatement(&arg)); EXPECT_CALL(*m_builder, addConstValue(Value())); EXPECT_CALL(*m_builder, endIf()); - compiler->moveToIf(compiler->input("SUBSTACK")->valueBlock()); + compiler->moveToIf(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; }); auto substack1 = std::make_shared("", "substack"); substack1->setParent(if2); - substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(Value()); }); + substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); }); auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack1); if2->addInput(input); - compile(compiler, if1); + compile(m_compiler.get(), if1); } TEST_F(CompilerTest, MoveToIfElse) { - Compiler compiler(&m_engine, &m_target); auto if1 = std::make_shared("", "if"); - if1->setCompileFunction([](Compiler *compiler) { + if1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); 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); + compiler->moveToIfElse(&arg, nullptr, nullptr); + return nullptr; }); auto if2 = std::make_shared("", "if"); if1->setNext(if2); if2->setParent(if1); - if2->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginIfStatement()); + if2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginIfStatement(&arg)); 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()); + compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); + return nullptr; }); auto substack1 = std::make_shared("", "substack"); substack1->setParent(if2); - substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); }); auto substack2 = std::make_shared("", "substack"); substack2->setParent(if2); - substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); }); auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack1); @@ -425,29 +1194,35 @@ TEST_F(CompilerTest, MoveToIfElse) auto if3 = std::make_shared("", "if"); if2->setNext(if3); if3->setParent(if2); - if3->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginIfStatement()).Times(3); + if3->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + 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()); + compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); + return nullptr; }); // if auto ifSubstack1 = std::make_shared("", "if"); ifSubstack1->setParent(if3); - ifSubstack1->setCompileFunction([](Compiler *compiler) { compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); }); + ifSubstack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); + return nullptr; + }); substack1 = std::make_shared("", "substack"); substack1->setParent(ifSubstack1); - substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); }); substack2 = std::make_shared("", "substack"); substack2->setParent(ifSubstack1); - substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); }); input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack1); @@ -459,15 +1234,19 @@ TEST_F(CompilerTest, MoveToIfElse) // else auto ifSubstack2 = std::make_shared("", "if"); ifSubstack2->setParent(if3); - ifSubstack2->setCompileFunction([](Compiler *compiler) { compiler->moveToIfElse(compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); }); + ifSubstack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), compiler->input("SUBSTACK2")->valueBlock()); + return nullptr; + }); substack1 = std::make_shared("", "substack"); substack1->setParent(ifSubstack2); - substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(3); }); + substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(3); }); substack2 = std::make_shared("", "substack"); substack2->setParent(ifSubstack2); - substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(4); }); + substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(4); }); input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack1); @@ -488,17 +1267,19 @@ TEST_F(CompilerTest, MoveToIfElse) auto if4 = std::make_shared("", "if"); if3->setNext(if4); if4->setParent(if3); - if4->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginIfStatement()); + if4->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginIfStatement(&arg)); EXPECT_CALL(*m_builder, beginElseBranch()); EXPECT_CALL(*m_builder, addConstValue(Value(2))); EXPECT_CALL(*m_builder, endIf()); - compiler->moveToIfElse(nullptr, compiler->input("SUBSTACK2")->valueBlock()); + compiler->moveToIfElse(&arg, nullptr, compiler->input("SUBSTACK2")->valueBlock()); + return nullptr; }); substack2 = std::make_shared("", "substack"); substack2->setParent(if4); - substack2->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + substack2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); }); input = std::make_shared("SUBSTACK2", Input::Type::NoShadow); input->setValueBlock(substack2); @@ -508,17 +1289,19 @@ TEST_F(CompilerTest, MoveToIfElse) auto if5 = std::make_shared("", "if"); if4->setNext(if5); if5->setParent(if4); - if5->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginIfStatement()); + if5->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginIfStatement(&arg)); 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); + compiler->moveToIfElse(&arg, compiler->input("SUBSTACK")->valueBlock(), nullptr); + return nullptr; }); substack1 = std::make_shared("", "substack"); substack1->setParent(if5); - substack1->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + substack1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); }); input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack1); @@ -528,36 +1311,39 @@ TEST_F(CompilerTest, MoveToIfElse) auto block = std::make_shared("", ""); block->setParent(if5); if5->setNext(block); - block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); }); EXPECT_CALL(*m_builder, addConstValue(Value("after"))); - compile(compiler, if1); + compile(m_compiler.get(), 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()); + l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginRepeatLoop(&arg)); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToRepeatLoop(nullptr); + compiler->moveToRepeatLoop(&arg, nullptr); + return nullptr; }); auto l2 = std::make_shared("", "loop"); l1->setNext(l2); l2->setParent(l1); - l2->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginRepeatLoop()); + l2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginRepeatLoop(&arg)); EXPECT_CALL(*m_builder, addConstValue(Value(2))); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock()); + compiler->moveToRepeatLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; }); auto substack = std::make_shared("", "substack"); substack->setParent(l2); - substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); }); auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack); @@ -567,21 +1353,27 @@ TEST_F(CompilerTest, MoveToRepeatLoop) auto l3 = std::make_shared("", "loop"); l2->setNext(l3); l3->setParent(l2); - l3->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginRepeatLoop()).Times(2); + l3->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + 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()); + compiler->moveToRepeatLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; }); // Begin loop auto loopSubstack = std::make_shared("", "loop"); loopSubstack->setParent(l3); - loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToRepeatLoop(compiler->input("SUBSTACK")->valueBlock()); }); + loopSubstack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + compiler->moveToRepeatLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; + }); substack = std::make_shared("", "substack"); substack->setParent(loopSubstack); - substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); }); input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack); @@ -596,46 +1388,51 @@ TEST_F(CompilerTest, MoveToRepeatLoop) auto l4 = std::make_shared("", "loop"); l3->setNext(l4); l4->setParent(l3); - l4->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginRepeatLoop()); + l4->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginRepeatLoop(&arg)); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToRepeatLoop(nullptr); + compiler->moveToRepeatLoop(&arg, nullptr); + return nullptr; }); // Code after the loop auto block = std::make_shared("", ""); block->setParent(l4); l4->setNext(block); - block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); }); EXPECT_CALL(*m_builder, addConstValue(Value("after"))); - compile(compiler, l1); + compile(m_compiler.get(), 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()); + l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginWhileLoop(&arg)); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToWhileLoop(nullptr); + compiler->moveToWhileLoop(&arg, nullptr); + return nullptr; }); auto l2 = std::make_shared("", "loop"); l1->setNext(l2); l2->setParent(l1); - l2->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginWhileLoop()); + l2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginWhileLoop(&arg)); EXPECT_CALL(*m_builder, addConstValue(Value(2))); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock()); + compiler->moveToWhileLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; }); auto substack = std::make_shared("", "substack"); substack->setParent(l2); - substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); }); auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack); @@ -645,21 +1442,27 @@ TEST_F(CompilerTest, MoveToWhileLoop) auto l3 = std::make_shared("", "loop"); l2->setNext(l3); l3->setParent(l2); - l3->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginWhileLoop()).Times(2); + l3->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + 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()); + compiler->moveToWhileLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; }); // Begin loop auto loopSubstack = std::make_shared("", "loop"); loopSubstack->setParent(l3); - loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToWhileLoop(compiler->input("SUBSTACK")->valueBlock()); }); + loopSubstack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + compiler->moveToWhileLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; + }); substack = std::make_shared("", "substack"); substack->setParent(loopSubstack); - substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); }); input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack); @@ -674,46 +1477,51 @@ TEST_F(CompilerTest, MoveToWhileLoop) auto l4 = std::make_shared("", "loop"); l3->setNext(l4); l4->setParent(l3); - l4->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginWhileLoop()); + l4->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginWhileLoop(&arg)); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToWhileLoop(nullptr); + compiler->moveToWhileLoop(&arg, nullptr); + return nullptr; }); // Code after the loop auto block = std::make_shared("", ""); block->setParent(l4); l4->setNext(block); - block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); }); EXPECT_CALL(*m_builder, addConstValue(Value("after"))); - compile(compiler, l1); + compile(m_compiler.get(), 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()); + l1->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg)); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToRepeatUntilLoop(nullptr); + compiler->moveToRepeatUntilLoop(&arg, nullptr); + return nullptr; }); auto l2 = std::make_shared("", "loop"); l1->setNext(l2); l2->setParent(l1); - l2->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginRepeatUntilLoop()); + l2->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg)); EXPECT_CALL(*m_builder, addConstValue(Value(2))); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock()); + compiler->moveToRepeatUntilLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; }); auto substack = std::make_shared("", "substack"); substack->setParent(l2); - substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(2); }); + substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(2); }); auto input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack); @@ -723,21 +1531,27 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop) auto l3 = std::make_shared("", "loop"); l2->setNext(l3); l3->setParent(l2); - l3->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginRepeatUntilLoop()).Times(2); + l3->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + 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()); + compiler->moveToRepeatUntilLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; }); // Begin loop auto loopSubstack = std::make_shared("", "loop"); loopSubstack->setParent(l3); - loopSubstack->setCompileFunction([](Compiler *compiler) { compiler->moveToRepeatUntilLoop(compiler->input("SUBSTACK")->valueBlock()); }); + loopSubstack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + compiler->moveToRepeatUntilLoop(&arg, compiler->input("SUBSTACK")->valueBlock()); + return nullptr; + }); substack = std::make_shared("", "substack"); substack->setParent(loopSubstack); - substack->setCompileFunction([](Compiler *compiler) { compiler->addConstValue(1); }); + substack->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(1); }); input = std::make_shared("SUBSTACK", Input::Type::NoShadow); input->setValueBlock(substack); @@ -752,73 +1566,112 @@ TEST_F(CompilerTest, MoveToRepeatUntilLoop) auto l4 = std::make_shared("", "loop"); l3->setNext(l4); l4->setParent(l3); - l4->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginRepeatUntilLoop()); + l4->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + CompilerValue arg(Compiler::StaticType::Unknown); + EXPECT_CALL(*m_builder, beginRepeatUntilLoop(&arg)); EXPECT_CALL(*m_builder, endLoop()); - compiler->moveToRepeatUntilLoop(nullptr); + compiler->moveToRepeatUntilLoop(&arg, nullptr); + return nullptr; }); // Code after the loop auto block = std::make_shared("", ""); block->setParent(l4); l4->setNext(block); - block->setCompileFunction([](Compiler *compiler) { compiler->addConstValue("after"); }); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue("after"); }); EXPECT_CALL(*m_builder, addConstValue(Value("after"))); - compile(compiler, l1); + compile(m_compiler.get(), l1); } -TEST_F(CompilerTest, BeginLoopCondition) +TEST_F(CompilerTest, CreateYield) { - Compiler compiler(&m_engine, &m_target); - auto block = std::make_shared("a", ""); - block->setCompileFunction([](Compiler *compiler) { - EXPECT_CALL(*m_builder, beginLoopCondition()); - compiler->beginLoopCondition(); + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_CALL(*m_builder, yield()); + compiler->createYield(); + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateStop) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_CALL(*m_builder, createStop()); + compiler->createStop(); + return nullptr; + }); + + compile(m_compiler.get(), block); +} + +TEST_F(CompilerTest, CreateProcedureCall) +{ + + auto block = std::make_shared("", ""); + + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + BlockPrototype prototype; + CompilerValue arg1(Compiler::StaticType::Unknown); + CompilerValue arg2(Compiler::StaticType::Unknown); + Compiler::Args args = { &arg1, &arg2 }; + + EXPECT_CALL(*m_builder, createProcedureCall(&prototype, args)); + compiler->createProcedureCall(&prototype, args); + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), 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()); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_EQ(compiler->input("INVALID"), nullptr); + EXPECT_EQ(compiler->input("TEST"), compiler->block()->inputAt(0).get()); + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, Field) { - Compiler compiler(&m_engine, &m_target); + auto block = std::make_shared("a", ""); block->addField(std::make_shared("TEST", "test")); - block->setCompileFunction([](Compiler *compiler) { - ASSERT_EQ(compiler->field("INVALID"), nullptr); - ASSERT_EQ(compiler->field("TEST"), compiler->block()->fieldAt(0).get()); + block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { + EXPECT_EQ(compiler->field("INVALID"), nullptr); + EXPECT_EQ(compiler->field("TEST"), compiler->block()->fieldAt(0).get()); + return nullptr; }); - compile(compiler, block); + compile(m_compiler.get(), block); } TEST_F(CompilerTest, UnsupportedBlocks) { auto valueBlock1 = std::make_shared("v1", "value_block1"); auto valueBlock2 = std::make_shared("v2", "value_block2"); - valueBlock2->setCompileFunction([](Compiler *) {}); + valueBlock2->setCompileFunction([](Compiler *) -> CompilerValue * { return nullptr; }); auto valueBlock3 = std::make_shared("v3", "value_block3"); auto valueBlock4 = std::make_shared("v4", "value_block4"); - valueBlock4->setCompileFunction([](Compiler *) {}); + valueBlock4->setCompileFunction([](Compiler *) -> CompilerValue * { return nullptr; }); auto valueBlock5 = std::make_shared("v5", "value_block5"); auto valueBlock6 = std::make_shared("v6", "value_block6"); - valueBlock6->setCompileFunction([](Compiler *) {}); + valueBlock6->setCompileFunction([](Compiler *) -> CompilerValue * { return nullptr; }); auto block1 = std::make_shared("b1", "block1"); @@ -845,13 +1698,14 @@ TEST_F(CompilerTest, UnsupportedBlocks) auto input6 = std::make_shared("INPUT6", Input::Type::ObscuredShadow); input6->setValueBlock(valueBlock6); block3->addInput(input6); - block3->setCompileFunction([](Compiler *compiler) { + block3->setCompileFunction([](Compiler *compiler) -> CompilerValue * { compiler->addInput("INPUT1"); compiler->addInput("INPUT2"); compiler->addInput("INPUT3"); compiler->addInput("INPUT4"); compiler->addInput("INPUT5"); compiler->addInput("INPUT6"); + return nullptr; }); block3->setParent(block2); block2->setNext(block3); @@ -860,9 +1714,46 @@ TEST_F(CompilerTest, UnsupportedBlocks) block4->setParent(block3); block3->setNext(block4); + EXPECT_CALL(*m_builder, addConstValue).WillRepeatedly(Return(nullptr)); + compile(m_compiler.get(), block1); + + ASSERT_EQ(m_compiler->unsupportedBlocks(), std::unordered_set({ "block1", "block2", "value_block1", "value_block3", "value_block5", "block4" })); +} + +TEST_F(CompilerTest, Procedure) +{ + { + auto block = std::make_shared("", ""); + auto customBlock = std::make_shared("", ""); + customBlock->mutationPrototype()->setProcCode(""); + + auto input = std::make_shared("custom_block", Input::Type::ObscuredShadow); + input->setValueBlock(customBlock); + block->addInput(input); + + compile(m_compiler.get(), block, nullptr); + } + + { + auto block = std::make_shared("", ""); + auto customBlock = std::make_shared("", ""); + customBlock->mutationPrototype()->setProcCode("test"); + + auto input = std::make_shared("custom_block", Input::Type::ObscuredShadow); + input->setValueBlock(customBlock); + block->addInput(input); + + compile(m_compiler.get(), block, customBlock->mutationPrototype()); + } +} + +TEST_F(CompilerTest, Preoptimize) +{ + auto ctx = std::make_shared(&m_engine, &m_target); + + EXPECT_CALL(m_builderFactory, createCtx(&m_engine, &m_target)).WillOnce(Return(ctx)); Compiler compiler(&m_engine, &m_target); - EXPECT_CALL(*m_builder, addConstValue).WillRepeatedly(Return()); - compile(compiler, block1); - ASSERT_EQ(compiler.unsupportedBlocks(), std::unordered_set({ "block1", "block2", "value_block1", "value_block3", "value_block5", "block4" })); + EXPECT_CALL(*ctx, preoptimize()); + compiler.preoptimize(); } diff --git a/test/dev/compiler/compilerconstant_test.cpp b/test/dev/compiler/compilerconstant_test.cpp new file mode 100644 index 00000000..c7c58720 --- /dev/null +++ b/test/dev/compiler/compilerconstant_test.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +using namespace libscratchcpp; + +TEST(CompilerConstantTest, Constructors) +{ + { + CompilerConstant v(Compiler::StaticType::Number, 5); + ASSERT_EQ(v.type(), Compiler::StaticType::Number); + ASSERT_EQ(v.value(), 5); + } + + { + CompilerConstant v(Compiler::StaticType::String, "test"); + ASSERT_EQ(v.type(), Compiler::StaticType::String); + ASSERT_EQ(v.value(), "test"); + } + + { + CompilerConstant v(Compiler::StaticType::Unknown, true); + ASSERT_EQ(v.type(), Compiler::StaticType::Unknown); + ASSERT_EQ(v.value(), true); + } +} + +TEST(CompilerConstantTest, IsConst) +{ + CompilerConstant v(Compiler::StaticType::Unknown, Value()); + ASSERT_TRUE(v.isConst()); +} diff --git a/test/dev/compiler/compilercontext_test.cpp b/test/dev/compiler/compilercontext_test.cpp new file mode 100644 index 00000000..0cc611b5 --- /dev/null +++ b/test/dev/compiler/compilercontext_test.cpp @@ -0,0 +1,14 @@ +#include +#include +#include + +using namespace libscratchcpp; + +TEST(CompilerContextTest, Constructors) +{ + EngineMock engine; + TargetMock target; + CompilerContext ctx(&engine, &target); + ASSERT_EQ(ctx.engine(), &engine); + ASSERT_EQ(ctx.target(), &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/compiler/compilervalue_test.cpp b/test/dev/compiler/compilervalue_test.cpp new file mode 100644 index 00000000..a5256ba1 --- /dev/null +++ b/test/dev/compiler/compilervalue_test.cpp @@ -0,0 +1,42 @@ +#include +#include + +using namespace libscratchcpp; + +TEST(CompilerValueTest, Constructors) +{ + { + CompilerValue v(Compiler::StaticType::Number); + ASSERT_EQ(v.type(), Compiler::StaticType::Number); + } + + { + CompilerValue v(Compiler::StaticType::String); + ASSERT_EQ(v.type(), Compiler::StaticType::String); + } + + { + CompilerValue v(Compiler::StaticType::Unknown); + ASSERT_EQ(v.type(), Compiler::StaticType::Unknown); + } +} + +TEST(CompilerValueTest, Type) +{ + CompilerValue v(Compiler::StaticType::Unknown); + + v.setType(Compiler::StaticType::String); + ASSERT_EQ(v.type(), Compiler::StaticType::String); + + v.setType(Compiler::StaticType::Bool); + ASSERT_EQ(v.type(), Compiler::StaticType::Bool); + + v.setType(Compiler::StaticType::Unknown); + ASSERT_EQ(v.type(), Compiler::StaticType::Unknown); +} + +TEST(CompilerValueTest, IsConst) +{ + CompilerValue v(Compiler::StaticType::Unknown); + ASSERT_FALSE(v.isConst()); +} diff --git a/test/dev/executioncontext/CMakeLists.txt b/test/dev/executioncontext/CMakeLists.txt index ef216403..ace0b73c 100644 --- a/test/dev/executioncontext/CMakeLists.txt +++ b/test/dev/executioncontext/CMakeLists.txt @@ -6,7 +6,9 @@ add_executable( target_link_libraries( executioncontext_test GTest::gtest_main + GTest::gmock_main scratchcpp + scratchcpp_mocks ) gtest_discover_tests(executioncontext_test) diff --git a/test/dev/executioncontext/executioncontext_test.cpp b/test/dev/executioncontext/executioncontext_test.cpp index 3429ffef..d6945813 100644 --- a/test/dev/executioncontext/executioncontext_test.cpp +++ b/test/dev/executioncontext/executioncontext_test.cpp @@ -1,5 +1,10 @@ #include -#include +#include +#include + +#include +#include +#include #include "../../common.h" @@ -7,7 +12,42 @@ using namespace libscratchcpp; TEST(ExecutionContextTest, Constructor) { - Target target; - ExecutionContext ctx(&target); - ASSERT_EQ(ctx.target(), &target); + EngineMock engine; + Thread thread(nullptr, &engine, nullptr); + ExecutionContext ctx(&thread); + ASSERT_EQ(ctx.thread(), &thread); + ASSERT_EQ(ctx.engine(), &engine); +} + +TEST(ExecutionContextTest, Promise) +{ + ExecutionContext ctx(nullptr); + ASSERT_EQ(ctx.promise(), nullptr); + + auto promise = std::make_shared(); + ctx.setPromise(promise); + ASSERT_EQ(ctx.promise(), promise); + + ctx.setPromise(nullptr); + ASSERT_EQ(ctx.promise(), nullptr); +} + +TEST(ExecutionContextTest, StackTimer) +{ + ExecutionContext ctx(nullptr); + ASSERT_TRUE(ctx.stackTimer()); + + StackTimerMock timer; + ctx.setStackTimer(&timer); + ASSERT_EQ(ctx.stackTimer(), &timer); +} + +TEST(ExecutionContextTest, Rng) +{ + ExecutionContext ctx(nullptr); + ASSERT_TRUE(ctx.rng()); + + RandomGeneratorMock rng; + ctx.setRng(&rng); + ASSERT_EQ(ctx.rng(), &rng); } diff --git a/test/dev/llvm/llvmcodebuilder_test.cpp b/test/dev/llvm/llvmcodebuilder_test.cpp index 6715cc0e..e107097b 100644 --- a/test/dev/llvm/llvmcodebuilder_test.cpp +++ b/test/dev/llvm/llvmcodebuilder_test.cpp @@ -1,8 +1,19 @@ #include #include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include +#include +#include #include "testfunctions.h" @@ -14,36 +25,374 @@ using ::testing::Eq; class LLVMCodeBuilderTest : public testing::Test { public: + enum class OpType + { + Add, + Sub, + Mul, + Div, + Random, + RandomInt, + CmpEQ, + CmpGT, + CmpLT, + StrCmpEQCS, + StrCmpEQCI, + And, + Or, + Not, + Mod, + Round, + Abs, + Floor, + Ceil, + Sqrt, + Sin, + Cos, + Tan, + Asin, + Acos, + Atan, + Ln, + Log10, + Exp, + Exp10 + }; + void SetUp() override { - test_function(nullptr, nullptr); // force dependency + test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency + } + + void createBuilder(Target *target, BlockPrototype *procedurePrototype) + { + if (m_contexts.find(target) == m_contexts.cend() || !target) + m_contexts[target] = std::make_unique(&m_engine, target); + + m_builder = std::make_unique(m_contexts[target].get(), procedurePrototype); + } + + void createBuilder(Target *target, bool warp) + { + m_procedurePrototype = std::make_shared("test"); + m_procedurePrototype->setWarp(warp); + createBuilder(target, m_procedurePrototype.get()); } - void createBuilder(bool warp) { m_builder = std::make_unique("test", warp); } + void createBuilder(bool warp) { createBuilder(nullptr, warp); } - void callConstFuncForType(ValueType type) + CompilerValue *callConstFuncForType(ValueType type, CompilerValue *arg) { switch (type) { case ValueType::Number: - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - break; + return m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }, { arg }); case ValueType::Bool: - m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - break; + return m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { arg }); case ValueType::String: - m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }); - break; + return m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }, { arg }); + + default: + EXPECT_TRUE(false); + return nullptr; + } + } + + CompilerValue *addOp(OpType type, CompilerValue *arg1, CompilerValue *arg2) + { + switch (type) { + case OpType::Add: + return m_builder->createAdd(arg1, arg2); + + case OpType::Sub: + return m_builder->createSub(arg1, arg2); + + case OpType::Mul: + return m_builder->createMul(arg1, arg2); + + case OpType::Div: + return m_builder->createDiv(arg1, arg2); + + 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); + + case OpType::CmpGT: + return m_builder->createCmpGT(arg1, arg2); + + case OpType::CmpLT: + return m_builder->createCmpLT(arg1, arg2); + + case OpType::StrCmpEQCS: + return m_builder->createStrCmpEQ(arg1, arg2, true); + + case OpType::StrCmpEQCI: + return m_builder->createStrCmpEQ(arg1, arg2, false); + + case OpType::And: + return m_builder->createAnd(arg1, arg2); + + case OpType::Or: + return m_builder->createOr(arg1, arg2); + + case OpType::Mod: + return m_builder->createMod(arg1, arg2); + + default: + EXPECT_TRUE(false); + return nullptr; + } + } + + CompilerValue *addOp(OpType type, CompilerValue *arg) + { + switch (type) { + case OpType::Not: + return m_builder->createNot(arg); + + case OpType::Round: + return m_builder->createRound(arg); + + case OpType::Abs: + return m_builder->createAbs(arg); + + case OpType::Floor: + return m_builder->createFloor(arg); + + case OpType::Ceil: + return m_builder->createCeil(arg); + + case OpType::Sqrt: + return m_builder->createSqrt(arg); + + case OpType::Sin: + return m_builder->createSin(arg); + + case OpType::Cos: + return m_builder->createCos(arg); + + case OpType::Tan: + return m_builder->createTan(arg); + + case OpType::Asin: + return m_builder->createAsin(arg); + + case OpType::Acos: + return m_builder->createAcos(arg); + + case OpType::Atan: + return m_builder->createAtan(arg); + + case OpType::Ln: + return m_builder->createLn(arg); + + case OpType::Log10: + return m_builder->createLog10(arg); + + case OpType::Exp: + return m_builder->createExp(arg); + + case OpType::Exp10: + return m_builder->createExp10(arg); + + default: + EXPECT_TRUE(false); + return nullptr; + } + } + + Value doOp(OpType type, const Value &v1, const Value &v2) + { + switch (type) { + case OpType::Add: + return v1 + v2; + + case OpType::Sub: + return v1 - v2; + + case OpType::Mul: + return v1 * v2; + + case OpType::Div: + return v1 / v2; + + case OpType::Random: { + const double sum = v1.toDouble() + v2.toDouble(); + + if (std::isnan(sum) || std::isinf(sum)) + return sum; + + 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; + + case OpType::CmpGT: + return v1 > v2; + + case OpType::CmpLT: + return v1 < v2; + + case OpType::StrCmpEQCS: + return v1.toString() == v2.toString(); + + case OpType::StrCmpEQCI: { + // TODO: Use a custom function for string comparison + std::string str1 = v1.toString(); + std::string str2 = v2.toString(); + return strcasecmp(str1.c_str(), str2.c_str()) == 0; + } + + case OpType::And: + return v1.toBool() && v2.toBool(); + + case OpType::Or: + return v1.toBool() || v2.toBool(); + + case OpType::Mod: + return v1 % v2; + + default: + EXPECT_TRUE(false); + return Value(); + } + } + + Value doOp(OpType type, const Value &v) + { + switch (type) { + case OpType::Not: + return !v.toBool(); default: - FAIL(); - break; + EXPECT_TRUE(false); + return Value(); } } + void runOpTestCommon(OpType type, const Value &v1, const Value &v2) + { + createBuilder(true); + + CompilerValue *arg1 = m_builder->addConstValue(v1); + CompilerValue *arg2 = m_builder->addConstValue(v2); + CompilerValue *ret = addOp(type, arg1, arg2); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret }); + + arg1 = m_builder->addConstValue(v1); + arg1 = callConstFuncForType(v1.type(), arg1); + arg2 = m_builder->addConstValue(v2); + arg2 = callConstFuncForType(v2.type(), arg2); + ret = addOp(type, arg1, arg2); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret }); + + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + ctx->setRng(&m_rng); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + } + + void checkOpTest(const Value &v1, const Value &v2, const std::string &expected) + { + const std::string quotes1 = v1.isString() ? "\"" : ""; + const std::string quotes2 = v2.isString() ? "\"" : ""; + ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; + } + + void runOpTest(OpType type, const Value &v1, const Value &v2, const Value &expected) + { + std::string str = expected.toString(); + runOpTestCommon(type, v1, v2); + checkOpTest(v1, v2, str + '\n' + str + '\n'); + }; + + void runOpTest(OpType type, const Value &v1, const Value &v2) + { + runOpTestCommon(type, v1, v2); + std::string str = doOp(type, v1, v2).toString() + '\n'; + std::string expected = str + str; + checkOpTest(v1, v2, expected); + }; + + void runOpTest(OpType type, const Value &v) + { + createBuilder(true); + + CompilerValue *arg = m_builder->addConstValue(v); + CompilerValue *ret = addOp(type, arg); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret }); + + arg = m_builder->addConstValue(v); + arg = callConstFuncForType(v.type(), arg); + ret = addOp(type, arg); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { ret }); + + std::string str = doOp(type, v).toString() + '\n'; + std::string expected = str + str; + + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + const std::string quotes = v.isString() ? "\"" : ""; + ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "NOT: " << quotes << v.toString() << quotes; + }; + + void runUnaryNumOpTest(OpType type, const Value &v, double expectedResult) + { + createBuilder(true); + + CompilerValue *arg = m_builder->addConstValue(v); + CompilerValue *ret = addOp(type, arg); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { ret }); + + arg = m_builder->addConstValue(v); + arg = callConstFuncForType(v.type(), arg); + ret = addOp(type, arg); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { ret }); + + std::stringstream stream; + stream << expectedResult; + std::string str = stream.str() + '\n'; + std::string expected = str + str; + + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + const std::string quotes = v.isString() ? "\"" : ""; + ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes << v.toString() << quotes; + }; + + std::unordered_map> m_contexts; std::unique_ptr m_builder; + std::shared_ptr m_procedurePrototype; + EngineMock m_engine; TargetMock m_target; // NOTE: isStage() is used for call expectations + RandomGeneratorMock m_rng; }; TEST_F(LLVMCodeBuilderTest, FunctionCalls) @@ -52,26 +401,47 @@ TEST_F(LLVMCodeBuilderTest, FunctionCalls) for (bool warp : warpList) { createBuilder(warp); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - - m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - - m_builder->addConstValue("1"); - m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }); - m_builder->addConstValue("2"); - m_builder->addConstValue("3"); - m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }); - - m_builder->addConstValue("test"); - m_builder->addConstValue("4"); - m_builder->addConstValue("5"); - m_builder->addFunctionCall("test_function_3_args_ret", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); - static const std::string expected = + m_builder->addFunctionCall("test_empty_function", Compiler::StaticType::Void, {}, {}); + + CompilerValue *v = m_builder->addConstValue("test"); + m_builder->addFunctionCallWithCtx("test_ctx_function", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + + v = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {}); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("1"); + v = m_builder->addTargetFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v }); + CompilerValue *v1 = m_builder->addConstValue("2"); + CompilerValue *v2 = m_builder->addConstValue("3"); + m_builder + ->addTargetFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 }); + + v = m_builder->addConstValue("test"); + v1 = m_builder->addConstValue("4"); + v2 = m_builder->addConstValue("5"); + v = m_builder->addTargetFunctionCall( + "test_function_3_args_ret", + Compiler::StaticType::String, + { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, + { v, v1, v2 }); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + std::stringstream s; + s << ctx.get(); + std::string ctxPtr = s.str(); + + const std::string expected = + "empty\n" + ctxPtr + + "\n" + "test\n" "no_args\n" "no_args_ret\n" "1_arg no_args_output\n" @@ -92,28 +462,31 @@ TEST_F(LLVMCodeBuilderTest, ConstCasting) { createBuilder(true); - m_builder->addConstValue(5.2); - m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }); - m_builder->addConstValue("-24.156"); - m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }); + CompilerValue *v = m_builder->addConstValue(5.2); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + v = m_builder->addConstValue("-24.156"); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); - m_builder->addConstValue(true); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue(true); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); - m_builder->addConstValue(0); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue(0); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); - m_builder->addConstValue("false"); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue("false"); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); - m_builder->addConstValue("123"); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addConstValue("123"); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - m_builder->addConstValue("hello world"); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addConstValue("hello world"); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); static const std::string expected = "5.2\n" @@ -134,72 +507,75 @@ TEST_F(LLVMCodeBuilderTest, RawValueCasting) createBuilder(true); // Number -> number - m_builder->addConstValue(5.2); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }); + CompilerValue *v = m_builder->addConstValue(5.2); + v = callConstFuncForType(ValueType::Number, v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); // Number -> bool - m_builder->addConstValue(-24.156); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue(-24.156); + v = callConstFuncForType(ValueType::Number, v); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); - m_builder->addConstValue(0); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue(0); + v = callConstFuncForType(ValueType::Number, v); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); // Number -> string - m_builder->addConstValue(59.8); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addConstValue(59.8); + v = callConstFuncForType(ValueType::Number, v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); // Bool -> number - m_builder->addConstValue(true); - m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }); + v = m_builder->addConstValue(true); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); - m_builder->addConstValue(false); - m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }); + v = m_builder->addConstValue(false); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); // Bool -> bool - m_builder->addConstValue(true); - m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue(true); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); - m_builder->addConstValue(false); - m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue(false); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); // Bool -> string - m_builder->addConstValue(true); - m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addConstValue(true); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - m_builder->addConstValue(false); - m_builder->addFunctionCall("test_const_bool", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addConstValue(false); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); // String -> number - m_builder->addConstValue("5.2"); - m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }); + v = m_builder->addConstValue("5.2"); + v = callConstFuncForType(ValueType::String, v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); // String -> bool - m_builder->addConstValue("abc"); - m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue("abc"); + v = callConstFuncForType(ValueType::String, v); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); - m_builder->addConstValue("false"); - m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }); + v = m_builder->addConstValue("false"); + v = callConstFuncForType(ValueType::String, v); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); // String -> string - m_builder->addConstValue("hello world"); - m_builder->addFunctionCall("test_const_string", Compiler::StaticType::String, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addConstValue("hello world"); + v = callConstFuncForType(ValueType::String, v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); static const std::string expected = "5.2\n" @@ -224,1390 +600,5330 @@ TEST_F(LLVMCodeBuilderTest, RawValueCasting) TEST_F(LLVMCodeBuilderTest, Add) { - std::string expected; - - auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) { - createBuilder(true); + runOpTest(OpType::Add, 50, 25); + runOpTest(OpType::Add, -500, 25); + runOpTest(OpType::Add, -500, -25); + runOpTest(OpType::Add, "2.54", "6.28"); + runOpTest(OpType::Add, 2.54, "-6.28"); + runOpTest(OpType::Add, true, true); + runOpTest(OpType::Add, "Infinity", "Infinity"); + runOpTest(OpType::Add, "Infinity", "-Infinity"); + runOpTest(OpType::Add, "-Infinity", "Infinity"); + runOpTest(OpType::Add, "-Infinity", "-Infinity"); + runOpTest(OpType::Add, 1, "NaN"); + runOpTest(OpType::Add, "NaN", 1); +} - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createAdd(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); +TEST_F(LLVMCodeBuilderTest, Subtract) +{ + runOpTest(OpType::Sub, 50, 25); + runOpTest(OpType::Sub, -500, 25); + runOpTest(OpType::Sub, -500, -25); + runOpTest(OpType::Sub, "2.54", "6.28"); + runOpTest(OpType::Sub, 2.54, "-6.28"); + runOpTest(OpType::Sub, true, true); + runOpTest(OpType::Sub, "Infinity", "Infinity"); + runOpTest(OpType::Sub, "Infinity", "-Infinity"); + runOpTest(OpType::Sub, "-Infinity", "Infinity"); + runOpTest(OpType::Sub, "-Infinity", "-Infinity"); + runOpTest(OpType::Sub, 1, "NaN"); + runOpTest(OpType::Sub, "NaN", 1); +} - m_builder->addConstValue(v1); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addConstValue(v2); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->createAdd(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); +TEST_F(LLVMCodeBuilderTest, Multiply) +{ + runOpTest(OpType::Mul, 50, 2); + runOpTest(OpType::Mul, -500, 25); + runOpTest(OpType::Mul, "-500", -25); + runOpTest(OpType::Mul, "2.54", "6.28"); + runOpTest(OpType::Mul, true, true); + runOpTest(OpType::Mul, "Infinity", "Infinity"); + runOpTest(OpType::Mul, "Infinity", 0); + runOpTest(OpType::Mul, "Infinity", 2); + runOpTest(OpType::Mul, "Infinity", -2); + runOpTest(OpType::Mul, "Infinity", "-Infinity"); + runOpTest(OpType::Mul, "-Infinity", "Infinity"); + runOpTest(OpType::Mul, "-Infinity", 0); + runOpTest(OpType::Mul, "-Infinity", 2); + runOpTest(OpType::Mul, "-Infinity", -2); + runOpTest(OpType::Mul, "-Infinity", "-Infinity"); + runOpTest(OpType::Mul, 1, "NaN"); + runOpTest(OpType::Mul, "NaN", 1); +} - std::string str = Value(expectedResult).toString() + '\n'; - std::string expected = str + str; +TEST_F(LLVMCodeBuilderTest, Divide) +{ + runOpTest(OpType::Div, 50, 2); + runOpTest(OpType::Div, -500, 25); + runOpTest(OpType::Div, "-500", -25); + runOpTest(OpType::Div, "3.5", "2.5"); + runOpTest(OpType::Div, true, true); + runOpTest(OpType::Div, "Infinity", "Infinity"); + runOpTest(OpType::Div, "Infinity", 0); + runOpTest(OpType::Div, "Infinity", 2); + runOpTest(OpType::Div, "Infinity", -2); + runOpTest(OpType::Div, "Infinity", "-Infinity"); + runOpTest(OpType::Div, "-Infinity", "Infinity"); + runOpTest(OpType::Div, "-Infinity", 0); + runOpTest(OpType::Div, "-Infinity", 2); + runOpTest(OpType::Div, "-Infinity", -2); + runOpTest(OpType::Div, "-Infinity", "-Infinity"); + runOpTest(OpType::Div, 0, "Infinity"); + runOpTest(OpType::Div, 2, "Infinity"); + runOpTest(OpType::Div, -2, "Infinity"); + runOpTest(OpType::Div, 0, "-Infinity"); + runOpTest(OpType::Div, 2, "-Infinity"); + runOpTest(OpType::Div, -2, "-Infinity"); + runOpTest(OpType::Div, 1, "NaN"); + runOpTest(OpType::Div, "NaN", 1); + runOpTest(OpType::Div, 5, 0); + runOpTest(OpType::Div, -5, 0); + runOpTest(OpType::Div, 0, 0); +} - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); +TEST_F(LLVMCodeBuilderTest, Random) +{ + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18)); + runOpTest(OpType::Random, -45, 12); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes1 = v1.isString() ? "\"" : ""; - const std::string quotes2 = v2.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; - }; + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5)); + runOpTest(OpType::Random, -45.0, 12.0); - addOpTest(50, 25, 75); - addOpTest(-500, 25, -475); - addOpTest(-500, -25, -525); - addOpTest("2.54", "6.28", 8.82); - addOpTest(2.54, "-6.28", -3.74); - addOpTest(true, true, 2); - addOpTest("Infinity", "Infinity", std::numeric_limits::infinity()); - addOpTest("Infinity", "-Infinity", std::numeric_limits::quiet_NaN()); - addOpTest("-Infinity", "Infinity", std::numeric_limits::quiet_NaN()); - addOpTest("-Infinity", "-Infinity", -std::numeric_limits::infinity()); - addOpTest(1, "NaN", 1); - addOpTest("NaN", 1, 1); -} + EXPECT_CALL(m_rng, randintDouble(12, 6.05)).Times(3).WillRepeatedly(Return(3.486789)); + runOpTest(OpType::Random, 12, 6.05); -TEST_F(LLVMCodeBuilderTest, Subtract) -{ - std::string expected; + EXPECT_CALL(m_rng, randintDouble(-78.686, -45)).Times(3).WillRepeatedly(Return(-59.468873)); + runOpTest(OpType::Random, -78.686, -45); - auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) { - createBuilder(true); + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(-28.648764)); + runOpTest(OpType::Random, 6.05, -78.686); - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createSub(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, "-45", "12"); - m_builder->addConstValue(v1); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addConstValue(v2); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->createSub(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(5.2)); + runOpTest(OpType::Random, "-45.0", "12"); - std::string str = Value(expectedResult).toString() + '\n'; - std::string expected = str + str; + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(-15.5787)); + runOpTest(OpType::Random, "-45", "12.0"); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(2.587964)); + runOpTest(OpType::Random, "-45.0", "12.0"); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes1 = v1.isString() ? "\"" : ""; - const std::string quotes2 = v2.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; - }; + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764)); + runOpTest(OpType::Random, "6.05", "-78.686"); - addOpTest(50, 25, 25); - addOpTest(-500, 25, -525); - addOpTest(-500, -25, -475); - addOpTest("2.54", "6.28", -3.74); - addOpTest(2.54, "-6.28", 8.82); - addOpTest(true, true, 0); - addOpTest("Infinity", "Infinity", std::numeric_limits::quiet_NaN()); - addOpTest("Infinity", "-Infinity", std::numeric_limits::infinity()); - addOpTest("-Infinity", "Infinity", -std::numeric_limits::infinity()); - addOpTest("-Infinity", "-Infinity", std::numeric_limits::quiet_NaN()); - addOpTest(1, "NaN", 1); - addOpTest("NaN", 1, -1); -} + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, "-45", 12); -TEST_F(LLVMCodeBuilderTest, Multiply) -{ - std::string expected; + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, -45, "12"); - auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) { - createBuilder(true); + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(5.2)); + runOpTest(OpType::Random, "-45.0", 12); - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createMul(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randintDouble(-45, 12)).Times(3).WillRepeatedly(Return(-15.5787)); + runOpTest(OpType::Random, -45, "12.0"); - m_builder->addConstValue(v1); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addConstValue(v2); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->createMul(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764)); + runOpTest(OpType::Random, 6.05, "-78.686"); - std::string str = Value(expectedResult).toString() + '\n'; - std::string expected = str + str; + EXPECT_CALL(m_rng, randintDouble(6.05, -78.686)).Times(3).WillRepeatedly(Return(5.648764)); + runOpTest(OpType::Random, "6.05", -78.686); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + EXPECT_CALL(m_rng, randint(0, 1)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::Random, false, true); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes1 = v1.isString() ? "\"" : ""; - const std::string quotes2 = v2.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; - }; + EXPECT_CALL(m_rng, randint(1, 5)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::Random, true, 5); - addOpTest(50, 2, 100); - addOpTest(-500, 25, -12500); - addOpTest("-500", -25, 12500); - addOpTest("2.54", "6.28", 15.9512); - addOpTest(true, true, 1); - addOpTest("Infinity", "Infinity", std::numeric_limits::infinity()); - addOpTest("Infinity", 0, std::numeric_limits::quiet_NaN()); - addOpTest("Infinity", 2, std::numeric_limits::infinity()); - addOpTest("Infinity", -2, -std::numeric_limits::infinity()); - addOpTest("Infinity", "-Infinity", -std::numeric_limits::infinity()); - addOpTest("-Infinity", "Infinity", -std::numeric_limits::infinity()); - addOpTest("-Infinity", 0, std::numeric_limits::quiet_NaN()); - addOpTest("-Infinity", 2, -std::numeric_limits::infinity()); - addOpTest("-Infinity", -2, std::numeric_limits::infinity()); - addOpTest("-Infinity", "-Infinity", std::numeric_limits::infinity()); - addOpTest(1, "NaN", 0); - addOpTest("NaN", 1, 0); -} + EXPECT_CALL(m_rng, randint(8, 0)).Times(3).WillRepeatedly(Return(1)); + runOpTest(OpType::Random, 8, false); -TEST_F(LLVMCodeBuilderTest, Divide) -{ - std::string expected; + const double inf = std::numeric_limits::infinity(); + const double nan = std::numeric_limits::quiet_NaN(); - auto addOpTest = [this, &expected](Value v1, Value v2, double expectedResult) { - createBuilder(true); + EXPECT_CALL(m_rng, randint(0, 5)).Times(3).WillRepeatedly(Return(5)); + runOpTest(OpType::Random, nan, 5); - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createDiv(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randint(5, 0)).Times(3).WillRepeatedly(Return(3)); + runOpTest(OpType::Random, 5, nan); - m_builder->addConstValue(v1); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->addConstValue(v2); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->createDiv(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randint(0, 0)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::Random, nan, nan); - std::string str = Value(expectedResult).toString() + '\n'; - std::string expected = str + str; + EXPECT_CALL(m_rng, randint).WillRepeatedly(Return(0)); + EXPECT_CALL(m_rng, randintDouble).WillRepeatedly(Return(0)); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + runOpTest(OpType::Random, inf, 2, inf); + runOpTest(OpType::Random, -8, inf, inf); + runOpTest(OpType::Random, -inf, -2, -inf); + runOpTest(OpType::Random, 8, -inf, -inf); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes1 = v1.isString() ? "\"" : ""; - const std::string quotes2 = v2.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; - }; + runOpTest(OpType::Random, inf, 2.5, inf); + runOpTest(OpType::Random, -8.09, inf, inf); + runOpTest(OpType::Random, -inf, -2.5, -inf); + runOpTest(OpType::Random, 8.09, -inf, -inf); - addOpTest(50, 2, 25); - addOpTest(-500, 25, -20); - addOpTest("-500", -25, 20); - addOpTest("3.5", "2.5", 1.4); - addOpTest(true, true, 1); - addOpTest("Infinity", "Infinity", std::numeric_limits::quiet_NaN()); - addOpTest("Infinity", 0, std::numeric_limits::infinity()); - addOpTest("Infinity", 2, std::numeric_limits::infinity()); - addOpTest("Infinity", -2, -std::numeric_limits::infinity()); - addOpTest("Infinity", "-Infinity", std::numeric_limits::quiet_NaN()); - addOpTest("-Infinity", "Infinity", std::numeric_limits::quiet_NaN()); - addOpTest("-Infinity", 0, -std::numeric_limits::infinity()); - addOpTest("-Infinity", 2, -std::numeric_limits::infinity()); - addOpTest("-Infinity", -2, std::numeric_limits::infinity()); - addOpTest("-Infinity", "-Infinity", std::numeric_limits::quiet_NaN()); - addOpTest(0, "Infinity", 0); - addOpTest(2, "Infinity", 0); - addOpTest(-2, "Infinity", 0); - addOpTest(0, "-Infinity", 0); - addOpTest(2, "-Infinity", 0); - addOpTest(-2, "-Infinity", 0); - addOpTest(1, "NaN", std::numeric_limits::infinity()); - addOpTest("NaN", 1, 0); - addOpTest(5, 0, std::numeric_limits::infinity()); - addOpTest(-5, 0, -std::numeric_limits::infinity()); - addOpTest(0, 0, std::numeric_limits::quiet_NaN()); + runOpTest(OpType::Random, inf, inf, inf); + runOpTest(OpType::Random, -inf, -inf, -inf); + runOpTest(OpType::Random, inf, -inf, nan); + runOpTest(OpType::Random, -inf, inf, nan); } -TEST_F(LLVMCodeBuilderTest, EqualComparison) +TEST_F(LLVMCodeBuilderTest, RandomInt) { - auto addOpTest = [this](Value v1, Value v2) { - createBuilder(true); + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(-18)); + runOpTest(OpType::RandomInt, -45, 12); - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createCmpEQ(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(5)); + runOpTest(OpType::RandomInt, -45.0, 12.0); - m_builder->addConstValue(v1); - callConstFuncForType(v1.type()); - m_builder->addConstValue(v2); - callConstFuncForType(v2.type()); - m_builder->createCmpEQ(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + EXPECT_CALL(m_rng, randint(12, 6)).Times(3).WillRepeatedly(Return(3)); + runOpTest(OpType::RandomInt, 12, 6.05); - std::string str = Value(v1 == v2).toString() + '\n'; - std::string expected = str + str; + EXPECT_CALL(m_rng, randint(-78, -45)).Times(3).WillRepeatedly(Return(-59)); + runOpTest(OpType::RandomInt, -78.686, -45); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + EXPECT_CALL(m_rng, randint(-45, 12)).Times(3).WillRepeatedly(Return(0)); + runOpTest(OpType::RandomInt, "-45", "12"); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes1 = v1.isString() ? "\"" : ""; - const std::string quotes2 = v2.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; - }; + 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"); - addOpTest(10, 10); - addOpTest(10, 8); - addOpTest(8, 10); - - addOpTest(-4.25, -4.25); - addOpTest(-4.25, 5.312); - addOpTest(5.312, -4.25); - - addOpTest(true, true); - addOpTest(true, false); - addOpTest(false, true); - - addOpTest(1, true); - addOpTest(1, false); - - addOpTest("abC def", "abC def"); - addOpTest("abC def", "abc dEf"); - addOpTest("abC def", "ghi Jkl"); - addOpTest("abC def", "hello world"); - - addOpTest(" ", ""); - addOpTest(" ", "0"); - addOpTest(" ", 0); - addOpTest(0, " "); - addOpTest("", "0"); - addOpTest("", 0); - addOpTest(0, ""); - addOpTest("0", 0); - addOpTest(0, "0"); - - addOpTest(5.25, "5.25"); - addOpTest("5.25", 5.25); - addOpTest(5.25, " 5.25"); - addOpTest(" 5.25", 5.25); - addOpTest(5.25, "5.25 "); - addOpTest("5.25 ", 5.25); - addOpTest(5.25, " 5.25 "); - addOpTest(" 5.25 ", 5.25); - addOpTest(5.25, "5.26"); - addOpTest("5.26", 5.25); - addOpTest("5.25", "5.26"); - addOpTest(5, "5 "); - addOpTest("5 ", 5); - addOpTest(0, "1"); - addOpTest("1", 0); - addOpTest(0, "test"); - addOpTest("test", 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); + runOpTest(OpType::CmpEQ, 10, 8); + runOpTest(OpType::CmpEQ, 8, 10); + + runOpTest(OpType::CmpEQ, -4.25, -4.25); + runOpTest(OpType::CmpEQ, -4.25, 5.312); + runOpTest(OpType::CmpEQ, 5.312, -4.25); + + runOpTest(OpType::CmpEQ, true, true); + runOpTest(OpType::CmpEQ, true, false); + runOpTest(OpType::CmpEQ, false, true); + + runOpTest(OpType::CmpEQ, 1, true); + runOpTest(OpType::CmpEQ, 1, false); + + runOpTest(OpType::CmpEQ, "abC def", "abC def"); + runOpTest(OpType::CmpEQ, "abC def", "abc dEf"); + runOpTest(OpType::CmpEQ, "abC def", "ghi Jkl"); + runOpTest(OpType::CmpEQ, "abC def", "hello world"); + + runOpTest(OpType::CmpEQ, " ", ""); + runOpTest(OpType::CmpEQ, " ", "0"); + runOpTest(OpType::CmpEQ, " ", 0); + runOpTest(OpType::CmpEQ, 0, " "); + runOpTest(OpType::CmpEQ, "", "0"); + runOpTest(OpType::CmpEQ, "", 0); + runOpTest(OpType::CmpEQ, 0, ""); + runOpTest(OpType::CmpEQ, "0", 0); + runOpTest(OpType::CmpEQ, 0, "0"); + + runOpTest(OpType::CmpEQ, 5.25, "5.25"); + runOpTest(OpType::CmpEQ, "5.25", 5.25); + runOpTest(OpType::CmpEQ, 5.25, " 5.25"); + runOpTest(OpType::CmpEQ, " 5.25", 5.25); + runOpTest(OpType::CmpEQ, 5.25, "5.25 "); + runOpTest(OpType::CmpEQ, "5.25 ", 5.25); + runOpTest(OpType::CmpEQ, 5.25, " 5.25 "); + runOpTest(OpType::CmpEQ, " 5.25 ", 5.25); + runOpTest(OpType::CmpEQ, 5.25, "5.26"); + runOpTest(OpType::CmpEQ, "5.26", 5.25); + runOpTest(OpType::CmpEQ, "5.25", "5.26"); + runOpTest(OpType::CmpEQ, 5, "5 "); + runOpTest(OpType::CmpEQ, "5 ", 5); + runOpTest(OpType::CmpEQ, 0, "1"); + runOpTest(OpType::CmpEQ, "1", 0); + runOpTest(OpType::CmpEQ, 0, "test"); + runOpTest(OpType::CmpEQ, "test", 0); static const double inf = std::numeric_limits::infinity(); static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(inf, inf); - addOpTest(-inf, -inf); - addOpTest(nan, nan); - addOpTest(inf, -inf); - addOpTest(-inf, inf); - addOpTest(inf, nan); - addOpTest(nan, inf); - addOpTest(-inf, nan); - addOpTest(nan, -inf); - - addOpTest(5, inf); - addOpTest(5, -inf); - addOpTest(5, nan); - addOpTest(0, nan); - - addOpTest(true, "true"); - addOpTest("true", true); - addOpTest(false, "false"); - addOpTest("false", false); - addOpTest(false, "true"); - addOpTest("true", false); - addOpTest(true, "false"); - addOpTest("false", true); - addOpTest(true, "TRUE"); - addOpTest("TRUE", true); - addOpTest(false, "FALSE"); - addOpTest("FALSE", false); - - addOpTest(true, "00001"); - addOpTest("00001", true); - addOpTest(true, "00000"); - addOpTest("00000", true); - addOpTest(false, "00000"); - addOpTest("00000", false); - - addOpTest("true", 1); - addOpTest(1, "true"); - addOpTest("true", 0); - addOpTest(0, "true"); - addOpTest("false", 0); - addOpTest(0, "false"); - addOpTest("false", 1); - addOpTest(1, "false"); - - addOpTest("true", "TRUE"); - addOpTest("true", "FALSE"); - addOpTest("false", "FALSE"); - addOpTest("false", "TRUE"); - - addOpTest(true, inf); - addOpTest(true, -inf); - addOpTest(true, nan); - addOpTest(false, inf); - addOpTest(false, -inf); - addOpTest(false, nan); - - addOpTest("Infinity", inf); - addOpTest("Infinity", -inf); - addOpTest("Infinity", nan); - addOpTest("infinity", inf); - addOpTest("infinity", -inf); - addOpTest("infinity", nan); - addOpTest("-Infinity", inf); - addOpTest("-Infinity", -inf); - addOpTest("-Infinity", nan); - addOpTest("-infinity", inf); - addOpTest("-infinity", -inf); - addOpTest("-infinity", nan); - addOpTest("NaN", inf); - addOpTest("NaN", -inf); - addOpTest("NaN", nan); - addOpTest("nan", inf); - addOpTest("nan", -inf); - addOpTest("nan", nan); - - addOpTest(inf, "abc"); - addOpTest(inf, " "); - addOpTest(inf, ""); - addOpTest(inf, "0"); - addOpTest(-inf, "abc"); - addOpTest(-inf, " "); - addOpTest(-inf, ""); - addOpTest(-inf, "0"); - addOpTest(nan, "abc"); - addOpTest(nan, " "); - addOpTest(nan, ""); - addOpTest(nan, "0"); + runOpTest(OpType::CmpEQ, inf, inf); + runOpTest(OpType::CmpEQ, -inf, -inf); + runOpTest(OpType::CmpEQ, nan, nan); + runOpTest(OpType::CmpEQ, inf, -inf); + runOpTest(OpType::CmpEQ, -inf, inf); + runOpTest(OpType::CmpEQ, inf, nan); + runOpTest(OpType::CmpEQ, nan, inf); + runOpTest(OpType::CmpEQ, -inf, nan); + runOpTest(OpType::CmpEQ, nan, -inf); + + runOpTest(OpType::CmpEQ, 5, inf); + runOpTest(OpType::CmpEQ, 5, -inf); + runOpTest(OpType::CmpEQ, 5, nan); + runOpTest(OpType::CmpEQ, 0, nan); + + runOpTest(OpType::CmpEQ, true, "true"); + runOpTest(OpType::CmpEQ, "true", true); + runOpTest(OpType::CmpEQ, false, "false"); + runOpTest(OpType::CmpEQ, "false", false); + runOpTest(OpType::CmpEQ, false, "true"); + runOpTest(OpType::CmpEQ, "true", false); + runOpTest(OpType::CmpEQ, true, "false"); + runOpTest(OpType::CmpEQ, "false", true); + runOpTest(OpType::CmpEQ, true, "TRUE"); + runOpTest(OpType::CmpEQ, "TRUE", true); + runOpTest(OpType::CmpEQ, false, "FALSE"); + runOpTest(OpType::CmpEQ, "FALSE", false); + + runOpTest(OpType::CmpEQ, true, "00001"); + runOpTest(OpType::CmpEQ, "00001", true); + runOpTest(OpType::CmpEQ, true, "00000"); + runOpTest(OpType::CmpEQ, "00000", true); + runOpTest(OpType::CmpEQ, false, "00000"); + runOpTest(OpType::CmpEQ, "00000", false); + + runOpTest(OpType::CmpEQ, "true", 1); + runOpTest(OpType::CmpEQ, 1, "true"); + runOpTest(OpType::CmpEQ, "true", 0); + runOpTest(OpType::CmpEQ, 0, "true"); + runOpTest(OpType::CmpEQ, "false", 0); + runOpTest(OpType::CmpEQ, 0, "false"); + runOpTest(OpType::CmpEQ, "false", 1); + runOpTest(OpType::CmpEQ, 1, "false"); + + runOpTest(OpType::CmpEQ, "true", "TRUE"); + runOpTest(OpType::CmpEQ, "true", "FALSE"); + runOpTest(OpType::CmpEQ, "false", "FALSE"); + runOpTest(OpType::CmpEQ, "false", "TRUE"); + + runOpTest(OpType::CmpEQ, true, inf); + runOpTest(OpType::CmpEQ, true, -inf); + runOpTest(OpType::CmpEQ, true, nan); + runOpTest(OpType::CmpEQ, false, inf); + runOpTest(OpType::CmpEQ, false, -inf); + runOpTest(OpType::CmpEQ, false, nan); + + runOpTest(OpType::CmpEQ, "Infinity", inf); + runOpTest(OpType::CmpEQ, "Infinity", -inf); + runOpTest(OpType::CmpEQ, "Infinity", nan); + runOpTest(OpType::CmpEQ, "infinity", inf); + runOpTest(OpType::CmpEQ, "infinity", -inf); + runOpTest(OpType::CmpEQ, "infinity", nan); + runOpTest(OpType::CmpEQ, "-Infinity", inf); + runOpTest(OpType::CmpEQ, "-Infinity", -inf); + runOpTest(OpType::CmpEQ, "-Infinity", nan); + runOpTest(OpType::CmpEQ, "-infinity", inf); + runOpTest(OpType::CmpEQ, "-infinity", -inf); + runOpTest(OpType::CmpEQ, "-infinity", nan); + runOpTest(OpType::CmpEQ, "NaN", inf); + runOpTest(OpType::CmpEQ, "NaN", -inf); + runOpTest(OpType::CmpEQ, "NaN", nan); + runOpTest(OpType::CmpEQ, "nan", inf); + runOpTest(OpType::CmpEQ, "nan", -inf); + runOpTest(OpType::CmpEQ, "nan", nan); + + runOpTest(OpType::CmpEQ, inf, "abc"); + runOpTest(OpType::CmpEQ, inf, " "); + runOpTest(OpType::CmpEQ, inf, ""); + runOpTest(OpType::CmpEQ, inf, "0"); + runOpTest(OpType::CmpEQ, -inf, "abc"); + runOpTest(OpType::CmpEQ, -inf, " "); + runOpTest(OpType::CmpEQ, -inf, ""); + runOpTest(OpType::CmpEQ, -inf, "0"); + runOpTest(OpType::CmpEQ, nan, "abc"); + runOpTest(OpType::CmpEQ, nan, " "); + runOpTest(OpType::CmpEQ, nan, ""); + runOpTest(OpType::CmpEQ, nan, "0"); } TEST_F(LLVMCodeBuilderTest, GreaterAndLowerThanComparison) { - auto addOpTest = [this](Value v1, Value v2) { - // GT - createBuilder(true); - - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createCmpGT(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + std::vector opTypes = { OpType::CmpGT, OpType::CmpLT }; + + for (OpType type : opTypes) { + runOpTest(type, 10, 10); + runOpTest(type, 10, 8); + runOpTest(type, 8, 10); + + runOpTest(type, -4.25, -4.25); + runOpTest(type, -4.25, 5.312); + runOpTest(type, 5.312, -4.25); + + runOpTest(type, true, true); + runOpTest(type, true, false); + runOpTest(type, false, true); + + runOpTest(type, 1, true); + runOpTest(type, 1, false); + + runOpTest(type, "abC def", "abC def"); + runOpTest(type, "abC def", "abc dEf"); + runOpTest(type, "abC def", "ghi Jkl"); + runOpTest(type, "ghi Jkl", "abC def"); + runOpTest(type, "abC def", "hello world"); + + runOpTest(type, " ", ""); + runOpTest(type, " ", "0"); + runOpTest(type, " ", 0); + runOpTest(type, 0, " "); + runOpTest(type, "", "0"); + runOpTest(type, "", 0); + runOpTest(type, 0, ""); + runOpTest(type, "0", 0); + runOpTest(type, 0, "0"); + + runOpTest(type, 5.25, "5.25"); + runOpTest(type, "5.25", 5.25); + runOpTest(type, 5.25, " 5.25"); + runOpTest(type, " 5.25", 5.25); + runOpTest(type, 5.25, "5.25 "); + runOpTest(type, "5.25 ", 5.25); + runOpTest(type, 5.25, " 5.25 "); + runOpTest(type, " 5.25 ", 5.25); + runOpTest(type, 5.25, "5.26"); + runOpTest(type, "5.26", 5.25); + runOpTest(type, "5.25", "5.26"); + runOpTest(type, 5, "5 "); + runOpTest(type, "5 ", 5); + runOpTest(type, 0, "1"); + runOpTest(type, "1", 0); + runOpTest(type, 0, "test"); + runOpTest(type, "test", 0); + runOpTest(type, 55, "abc"); + runOpTest(type, "abc", 55); + + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); + + runOpTest(type, inf, inf); + runOpTest(type, -inf, -inf); + runOpTest(type, nan, nan); + runOpTest(type, inf, -inf); + runOpTest(type, -inf, inf); + runOpTest(type, inf, nan); + runOpTest(type, nan, inf); + runOpTest(type, -inf, nan); + runOpTest(type, nan, -inf); + + runOpTest(type, 5, inf); + runOpTest(type, inf, 5); + runOpTest(type, 5, -inf); + runOpTest(type, -inf, 5); + runOpTest(type, 5, nan); + runOpTest(type, nan, 5); + runOpTest(type, 0, nan); + runOpTest(type, nan, 0); + + runOpTest(type, true, "true"); + runOpTest(type, "true", true); + runOpTest(type, false, "false"); + runOpTest(type, "false", false); + runOpTest(type, false, "true"); + runOpTest(type, "true", false); + runOpTest(type, true, "false"); + runOpTest(type, "false", true); + runOpTest(type, true, "TRUE"); + runOpTest(type, "TRUE", true); + runOpTest(type, false, "FALSE"); + runOpTest(type, "FALSE", false); + + runOpTest(type, true, "00001"); + runOpTest(type, "00001", true); + runOpTest(type, true, "00000"); + runOpTest(type, "00000", true); + runOpTest(type, false, "00000"); + runOpTest(type, "00000", false); + + runOpTest(type, "true", 1); + runOpTest(type, 1, "true"); + runOpTest(type, "true", 0); + runOpTest(type, 0, "true"); + runOpTest(type, "false", 0); + runOpTest(type, 0, "false"); + runOpTest(type, "false", 1); + runOpTest(type, 1, "false"); + + runOpTest(type, "true", "TRUE"); + runOpTest(type, "true", "FALSE"); + runOpTest(type, "false", "FALSE"); + runOpTest(type, "false", "TRUE"); + + runOpTest(type, true, inf); + runOpTest(type, inf, true); + runOpTest(type, true, -inf); + runOpTest(type, -inf, true); + runOpTest(type, true, nan); + runOpTest(type, nan, true); + runOpTest(type, false, inf); + runOpTest(type, inf, false); + runOpTest(type, false, -inf); + runOpTest(type, -inf, false); + runOpTest(type, false, nan); + runOpTest(type, nan, false); + + runOpTest(type, "Infinity", inf); + runOpTest(type, "Infinity", -inf); + runOpTest(type, "Infinity", nan); + runOpTest(type, "infinity", inf); + runOpTest(type, "infinity", -inf); + runOpTest(type, "infinity", nan); + runOpTest(type, "-Infinity", inf); + runOpTest(type, "-Infinity", -inf); + runOpTest(type, "-Infinity", nan); + runOpTest(type, "-infinity", inf); + runOpTest(type, "-infinity", -inf); + runOpTest(type, "-infinity", nan); + runOpTest(type, "NaN", inf); + runOpTest(type, "NaN", -inf); + runOpTest(type, "NaN", nan); + runOpTest(type, "nan", inf); + runOpTest(type, "nan", -inf); + runOpTest(type, "nan", nan); + + runOpTest(type, inf, "abc"); + runOpTest(type, inf, " "); + runOpTest(type, inf, ""); + runOpTest(type, inf, "0"); + runOpTest(type, -inf, "abc"); + runOpTest(type, -inf, " "); + runOpTest(type, -inf, ""); + runOpTest(type, -inf, "0"); + runOpTest(type, nan, "abc"); + runOpTest(type, nan, " "); + runOpTest(type, nan, ""); + runOpTest(type, nan, "0"); + } +} - m_builder->addConstValue(v1); - callConstFuncForType(v1.type()); - m_builder->addConstValue(v2); - callConstFuncForType(v2.type()); - m_builder->createCmpGT(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); +TEST_F(LLVMCodeBuilderTest, StringEqualComparison) +{ + std::vector types = { OpType::StrCmpEQCS, OpType::StrCmpEQCI }; + + for (OpType type : types) { + runOpTest(type, 10, 10); + runOpTest(type, 10, 8); + runOpTest(type, 8, 10); + + runOpTest(type, -4.25, -4.25); + runOpTest(type, -4.25, 5.312); + runOpTest(type, 5.312, -4.25); + + runOpTest(type, true, true); + runOpTest(type, true, false); + runOpTest(type, false, true); + + runOpTest(type, 1, true); + runOpTest(type, 1, false); + + runOpTest(type, "abC def", "abC def"); + runOpTest(type, "abC def", "abc dEf"); + runOpTest(type, "abC def", "ghi Jkl"); + runOpTest(type, "abC def", "hello world"); + + runOpTest(type, " ", ""); + runOpTest(type, " ", "0"); + runOpTest(type, " ", 0); + runOpTest(type, 0, " "); + runOpTest(type, "", "0"); + runOpTest(type, "", 0); + runOpTest(type, 0, ""); + runOpTest(type, "0", 0); + runOpTest(type, 0, "0"); + + runOpTest(type, 5.25, "5.25"); + runOpTest(type, "5.25", 5.25); + runOpTest(type, 5.25, " 5.25"); + runOpTest(type, " 5.25", 5.25); + runOpTest(type, 5.25, "5.25 "); + runOpTest(type, "5.25 ", 5.25); + runOpTest(type, 5.25, " 5.25 "); + runOpTest(type, " 5.25 ", 5.25); + runOpTest(type, 5.25, "5.26"); + runOpTest(type, "5.26", 5.25); + runOpTest(type, "5.25", "5.26"); + runOpTest(type, 5, "5 "); + runOpTest(type, "5 ", 5); + runOpTest(type, 0, "1"); + runOpTest(type, "1", 0); + runOpTest(type, 0, "test"); + runOpTest(type, "test", 0); + + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); + + runOpTest(type, inf, inf); + runOpTest(type, -inf, -inf); + runOpTest(type, nan, nan); + runOpTest(type, inf, -inf); + runOpTest(type, -inf, inf); + runOpTest(type, inf, nan); + runOpTest(type, nan, inf); + runOpTest(type, -inf, nan); + runOpTest(type, nan, -inf); + + runOpTest(type, 5, inf); + runOpTest(type, 5, -inf); + runOpTest(type, 5, nan); + runOpTest(type, 0, nan); + + runOpTest(type, true, "true"); + runOpTest(type, "true", true); + runOpTest(type, false, "false"); + runOpTest(type, "false", false); + runOpTest(type, false, "true"); + runOpTest(type, "true", false); + runOpTest(type, true, "false"); + runOpTest(type, "false", true); + runOpTest(type, true, "TRUE"); + runOpTest(type, "TRUE", true); + runOpTest(type, false, "FALSE"); + runOpTest(type, "FALSE", false); + + runOpTest(type, true, "00001"); + runOpTest(type, "00001", true); + runOpTest(type, true, "00000"); + runOpTest(type, "00000", true); + runOpTest(type, false, "00000"); + runOpTest(type, "00000", false); + + runOpTest(type, "true", 1); + runOpTest(type, 1, "true"); + runOpTest(type, "true", 0); + runOpTest(type, 0, "true"); + runOpTest(type, "false", 0); + runOpTest(type, 0, "false"); + runOpTest(type, "false", 1); + runOpTest(type, 1, "false"); + + runOpTest(type, "true", "TRUE"); + runOpTest(type, "true", "FALSE"); + runOpTest(type, "false", "FALSE"); + runOpTest(type, "false", "TRUE"); + + runOpTest(type, true, inf); + runOpTest(type, true, -inf); + runOpTest(type, true, nan); + runOpTest(type, false, inf); + runOpTest(type, false, -inf); + runOpTest(type, false, nan); + + runOpTest(type, "Infinity", inf); + runOpTest(type, "Infinity", -inf); + runOpTest(type, "Infinity", nan); + runOpTest(type, "infinity", inf); + runOpTest(type, "infinity", -inf); + runOpTest(type, "infinity", nan); + runOpTest(type, "-Infinity", inf); + runOpTest(type, "-Infinity", -inf); + runOpTest(type, "-Infinity", nan); + runOpTest(type, "-infinity", inf); + runOpTest(type, "-infinity", -inf); + runOpTest(type, "-infinity", nan); + runOpTest(type, "NaN", inf); + runOpTest(type, "NaN", -inf); + runOpTest(type, "NaN", nan); + runOpTest(type, "nan", inf); + runOpTest(type, "nan", -inf); + runOpTest(type, "nan", nan); + + runOpTest(type, inf, "abc"); + runOpTest(type, inf, " "); + runOpTest(type, inf, ""); + runOpTest(type, inf, "0"); + runOpTest(type, -inf, "abc"); + runOpTest(type, -inf, " "); + runOpTest(type, -inf, ""); + runOpTest(type, -inf, "0"); + runOpTest(type, nan, "abc"); + runOpTest(type, nan, " "); + runOpTest(type, nan, ""); + runOpTest(type, nan, "0"); + } +} - std::string str = Value(v1 > v2).toString() + '\n'; - std::string expected = str + str; +TEST_F(LLVMCodeBuilderTest, AndOr) +{ + std::vector opTypes = { OpType::And, OpType::Or }; + + for (OpType type : opTypes) { + runOpTest(type, 10, 8); + runOpTest(type, -4.25, -4.25); + runOpTest(type, -4.25, 5.312); + + runOpTest(type, true, true); + runOpTest(type, true, false); + runOpTest(type, false, true); + + runOpTest(type, 1, true); + runOpTest(type, 1, false); + + runOpTest(type, "abc", "def"); + runOpTest(type, "true", "true"); + runOpTest(type, "true", "false"); + runOpTest(type, "false", "true"); + runOpTest(type, "false", "false"); + + runOpTest(type, 5.25, "5.25"); + runOpTest(type, "5.25", 5.25); + runOpTest(type, 0, "1"); + runOpTest(type, "1", 0); + runOpTest(type, 0, "test"); + runOpTest(type, "test", 0); + + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); + + runOpTest(type, inf, inf); + runOpTest(type, -inf, -inf); + runOpTest(type, nan, nan); + runOpTest(type, inf, -inf); + runOpTest(type, -inf, inf); + runOpTest(type, inf, nan); + runOpTest(type, nan, inf); + runOpTest(type, -inf, nan); + runOpTest(type, nan, -inf); + + runOpTest(type, true, "true"); + runOpTest(type, "true", true); + runOpTest(type, false, "false"); + runOpTest(type, "false", false); + runOpTest(type, false, "true"); + runOpTest(type, "true", false); + runOpTest(type, true, "false"); + runOpTest(type, "false", true); + runOpTest(type, true, "TRUE"); + runOpTest(type, "TRUE", true); + runOpTest(type, false, "FALSE"); + runOpTest(type, "FALSE", false); + + runOpTest(type, true, "00001"); + runOpTest(type, "00001", true); + runOpTest(type, true, "00000"); + runOpTest(type, "00000", true); + runOpTest(type, false, "00000"); + runOpTest(type, "00000", false); + + runOpTest(type, "true", 1); + runOpTest(type, 1, "true"); + runOpTest(type, "true", 0); + runOpTest(type, 0, "true"); + runOpTest(type, "false", 0); + runOpTest(type, 0, "false"); + runOpTest(type, "false", 1); + runOpTest(type, 1, "false"); + + runOpTest(type, "true", "TRUE"); + runOpTest(type, "true", "FALSE"); + runOpTest(type, "false", "FALSE"); + runOpTest(type, "false", "TRUE"); + + runOpTest(type, true, inf); + runOpTest(type, inf, true); + runOpTest(type, true, -inf); + runOpTest(type, -inf, true); + runOpTest(type, true, nan); + runOpTest(type, nan, true); + runOpTest(type, false, inf); + runOpTest(type, inf, false); + runOpTest(type, false, -inf); + runOpTest(type, -inf, false); + runOpTest(type, false, nan); + runOpTest(type, nan, false); + + runOpTest(type, "Infinity", inf); + runOpTest(type, "Infinity", -inf); + runOpTest(type, "Infinity", nan); + runOpTest(type, "infinity", inf); + runOpTest(type, "infinity", -inf); + runOpTest(type, "infinity", nan); + runOpTest(type, "-Infinity", inf); + runOpTest(type, "-Infinity", -inf); + runOpTest(type, "-Infinity", nan); + runOpTest(type, "-infinity", inf); + runOpTest(type, "-infinity", -inf); + runOpTest(type, "-infinity", nan); + runOpTest(type, "NaN", inf); + runOpTest(type, "NaN", -inf); + runOpTest(type, "NaN", nan); + runOpTest(type, "nan", inf); + runOpTest(type, "nan", -inf); + runOpTest(type, "nan", nan); + } +} - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); +TEST_F(LLVMCodeBuilderTest, Not) +{ + runOpTest(OpType::Not, 10); + runOpTest(OpType::Not, -4.25); + runOpTest(OpType::Not, 5.312); + runOpTest(OpType::Not, 1); + runOpTest(OpType::Not, 0); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes1 = v1.isString() ? "\"" : ""; - const std::string quotes2 = v2.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "GT: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; + runOpTest(OpType::Not, true); + runOpTest(OpType::Not, false); - // LT - createBuilder(true); + runOpTest(OpType::Not, "abc"); + runOpTest(OpType::Not, "5.25"); + runOpTest(OpType::Not, "0"); - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createCmpLT(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - m_builder->addConstValue(v1); - callConstFuncForType(v1.type()); - m_builder->addConstValue(v2); - callConstFuncForType(v2.type()); - m_builder->createCmpLT(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + runOpTest(OpType::Not, inf); + runOpTest(OpType::Not, -inf); + runOpTest(OpType::Not, nan); - str = Value(v1 < v2).toString() + '\n'; - expected = str + str; + runOpTest(OpType::Not, "true"); + runOpTest(OpType::Not, "false"); + runOpTest(OpType::Not, "TRUE"); + runOpTest(OpType::Not, "FALSE"); - code = m_builder->finalize(); - ctx = code->createExecutionContext(&m_target); + runOpTest(OpType::Not, "00001"); + runOpTest(OpType::Not, "00000"); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "LT: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; - }; + runOpTest(OpType::Not, "Infinity"); + runOpTest(OpType::Not, "infinity"); + runOpTest(OpType::Not, "-Infinity"); + runOpTest(OpType::Not, "-infinity"); + runOpTest(OpType::Not, "NaN"); + runOpTest(OpType::Not, "nan"); +} - addOpTest(10, 10); - addOpTest(10, 8); - addOpTest(8, 10); - - addOpTest(-4.25, -4.25); - addOpTest(-4.25, 5.312); - addOpTest(5.312, -4.25); - - addOpTest(true, true); - addOpTest(true, false); - addOpTest(false, true); - - addOpTest(1, true); - addOpTest(1, false); - - addOpTest("abC def", "abC def"); - addOpTest("abC def", "abc dEf"); - addOpTest("abC def", "ghi Jkl"); - addOpTest("ghi Jkl", "abC def"); - addOpTest("abC def", "hello world"); - - addOpTest(" ", ""); - addOpTest(" ", "0"); - addOpTest(" ", 0); - addOpTest(0, " "); - addOpTest("", "0"); - addOpTest("", 0); - addOpTest(0, ""); - addOpTest("0", 0); - addOpTest(0, "0"); - - addOpTest(5.25, "5.25"); - addOpTest("5.25", 5.25); - addOpTest(5.25, " 5.25"); - addOpTest(" 5.25", 5.25); - addOpTest(5.25, "5.25 "); - addOpTest("5.25 ", 5.25); - addOpTest(5.25, " 5.25 "); - addOpTest(" 5.25 ", 5.25); - addOpTest(5.25, "5.26"); - addOpTest("5.26", 5.25); - addOpTest("5.25", "5.26"); - addOpTest(5, "5 "); - addOpTest("5 ", 5); - addOpTest(0, "1"); - addOpTest("1", 0); - addOpTest(0, "test"); - addOpTest("test", 0); +TEST_F(LLVMCodeBuilderTest, Mod) +{ + runOpTest(OpType::Mod, 4, 3); + runOpTest(OpType::Mod, 3, 3); + runOpTest(OpType::Mod, 2, 3); + runOpTest(OpType::Mod, 1, 3); + runOpTest(OpType::Mod, 0, 3); + runOpTest(OpType::Mod, -1, 3); + runOpTest(OpType::Mod, -2, 3); + runOpTest(OpType::Mod, -3, 3); + runOpTest(OpType::Mod, -4, 3); + runOpTest(OpType::Mod, 4.75, 2); + runOpTest(OpType::Mod, -4.75, 2); + runOpTest(OpType::Mod, -4.75, -2); + runOpTest(OpType::Mod, 4.75, -2); + runOpTest(OpType::Mod, 5, 0); + runOpTest(OpType::Mod, -5, 0); + runOpTest(OpType::Mod, -2.5, "Infinity"); + runOpTest(OpType::Mod, -1.2, "-Infinity"); + runOpTest(OpType::Mod, 2.5, "Infinity"); + runOpTest(OpType::Mod, 1.2, "-Infinity"); + runOpTest(OpType::Mod, "Infinity", 2); + runOpTest(OpType::Mod, "-Infinity", 2); + runOpTest(OpType::Mod, "Infinity", -2); + runOpTest(OpType::Mod, "-Infinity", -2); + runOpTest(OpType::Mod, 3, "NaN"); + runOpTest(OpType::Mod, -3, "NaN"); + runOpTest(OpType::Mod, "NaN", 5); + runOpTest(OpType::Mod, "NaN", -5); +} +TEST_F(LLVMCodeBuilderTest, Round) +{ static const double inf = std::numeric_limits::infinity(); static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(inf, inf); - addOpTest(-inf, -inf); - addOpTest(nan, nan); - addOpTest(inf, -inf); - addOpTest(-inf, inf); - addOpTest(inf, nan); - addOpTest(nan, inf); - addOpTest(-inf, nan); - addOpTest(nan, -inf); - - addOpTest(5, inf); - addOpTest(inf, 5); - addOpTest(5, -inf); - addOpTest(-inf, 5); - addOpTest(5, nan); - addOpTest(nan, 5); - addOpTest(0, nan); - addOpTest(nan, 0); - - addOpTest(true, "true"); - addOpTest("true", true); - addOpTest(false, "false"); - addOpTest("false", false); - addOpTest(false, "true"); - addOpTest("true", false); - addOpTest(true, "false"); - addOpTest("false", true); - addOpTest(true, "TRUE"); - addOpTest("TRUE", true); - addOpTest(false, "FALSE"); - addOpTest("FALSE", false); - - addOpTest(true, "00001"); - addOpTest("00001", true); - addOpTest(true, "00000"); - addOpTest("00000", true); - addOpTest(false, "00000"); - addOpTest("00000", false); - - addOpTest("true", 1); - addOpTest(1, "true"); - addOpTest("true", 0); - addOpTest(0, "true"); - addOpTest("false", 0); - addOpTest(0, "false"); - addOpTest("false", 1); - addOpTest(1, "false"); - - addOpTest("true", "TRUE"); - addOpTest("true", "FALSE"); - addOpTest("false", "FALSE"); - addOpTest("false", "TRUE"); - - addOpTest(true, inf); - addOpTest(inf, true); - addOpTest(true, -inf); - addOpTest(-inf, true); - addOpTest(true, nan); - addOpTest(nan, true); - addOpTest(false, inf); - addOpTest(inf, false); - addOpTest(false, -inf); - addOpTest(-inf, false); - addOpTest(false, nan); - addOpTest(nan, false); - - addOpTest("Infinity", inf); - addOpTest("Infinity", -inf); - addOpTest("Infinity", nan); - addOpTest("infinity", inf); - addOpTest("infinity", -inf); - addOpTest("infinity", nan); - addOpTest("-Infinity", inf); - addOpTest("-Infinity", -inf); - addOpTest("-Infinity", nan); - addOpTest("-infinity", inf); - addOpTest("-infinity", -inf); - addOpTest("-infinity", nan); - addOpTest("NaN", inf); - addOpTest("NaN", -inf); - addOpTest("NaN", nan); - addOpTest("nan", inf); - addOpTest("nan", -inf); - addOpTest("nan", nan); - - addOpTest(inf, "abc"); - addOpTest(inf, " "); - addOpTest(inf, ""); - addOpTest(inf, "0"); - addOpTest(-inf, "abc"); - addOpTest(-inf, " "); - addOpTest(-inf, ""); - addOpTest(-inf, "0"); - addOpTest(nan, "abc"); - addOpTest(nan, " "); - addOpTest(nan, ""); - addOpTest(nan, "0"); + runUnaryNumOpTest(OpType::Round, 4.0, 4.0); + runUnaryNumOpTest(OpType::Round, 3.2, 3.0); + runUnaryNumOpTest(OpType::Round, 3.5, 4.0); + runUnaryNumOpTest(OpType::Round, 3.6, 4.0); + runUnaryNumOpTest(OpType::Round, -2.4, -2.0); + runUnaryNumOpTest(OpType::Round, -2.5, -2.0); + runUnaryNumOpTest(OpType::Round, -2.6, -3.0); + runUnaryNumOpTest(OpType::Round, -0.4, -0.0); + runUnaryNumOpTest(OpType::Round, -0.5, -0.0); + runUnaryNumOpTest(OpType::Round, -0.51, -1.0); + runUnaryNumOpTest(OpType::Round, inf, inf); + runUnaryNumOpTest(OpType::Round, -inf, -inf); + runUnaryNumOpTest(OpType::Round, nan, 0); } -TEST_F(LLVMCodeBuilderTest, AndOr) +TEST_F(LLVMCodeBuilderTest, Abs) { - auto addOpTest = [this](Value v1, Value v2) { - // And - createBuilder(true); - - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createAnd(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); - - m_builder->addConstValue(v1); - callConstFuncForType(v1.type()); - m_builder->addConstValue(v2); - callConstFuncForType(v2.type()); - m_builder->createAnd(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); - - std::string str = Value(v1.toBool() && v2.toBool()).toString() + '\n'; - std::string expected = str + str; + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + runUnaryNumOpTest(OpType::Abs, 4.0, 4.0); + runUnaryNumOpTest(OpType::Abs, 3.2, 3.2); + runUnaryNumOpTest(OpType::Abs, -2.0, 2.0); + runUnaryNumOpTest(OpType::Abs, -2.5, 2.5); + runUnaryNumOpTest(OpType::Abs, -2.6, 2.6); + runUnaryNumOpTest(OpType::Abs, 0.0, 0.0); + runUnaryNumOpTest(OpType::Abs, -0.0, 0.0); + runUnaryNumOpTest(OpType::Abs, inf, inf); + runUnaryNumOpTest(OpType::Abs, -inf, inf); + runUnaryNumOpTest(OpType::Abs, nan, 0); +} - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes1 = v1.isString() ? "\"" : ""; - const std::string quotes2 = v2.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "AND: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; +TEST_F(LLVMCodeBuilderTest, Floor) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - // Or - createBuilder(true); + runUnaryNumOpTest(OpType::Floor, 4.0, 4.0); + runUnaryNumOpTest(OpType::Floor, 3.2, 3.0); + runUnaryNumOpTest(OpType::Floor, 3.5, 3.0); + runUnaryNumOpTest(OpType::Floor, 3.6, 3.0); + runUnaryNumOpTest(OpType::Floor, 0.0, 0.0); + runUnaryNumOpTest(OpType::Floor, -0.0, -0.0); + runUnaryNumOpTest(OpType::Floor, -2.4, -3.0); + runUnaryNumOpTest(OpType::Floor, -2.5, -3.0); + runUnaryNumOpTest(OpType::Floor, -2.6, -3.0); + runUnaryNumOpTest(OpType::Floor, -0.4, -1.0); + runUnaryNumOpTest(OpType::Floor, -0.5, -1.0); + runUnaryNumOpTest(OpType::Floor, -0.51, -1.0); + runUnaryNumOpTest(OpType::Floor, inf, inf); + runUnaryNumOpTest(OpType::Floor, -inf, -inf); + runUnaryNumOpTest(OpType::Floor, nan, 0); +} - m_builder->addConstValue(v1); - m_builder->addConstValue(v2); - m_builder->createOr(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); +TEST_F(LLVMCodeBuilderTest, Ceil) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - m_builder->addConstValue(v1); - callConstFuncForType(v1.type()); - m_builder->addConstValue(v2); - callConstFuncForType(v2.type()); - m_builder->createOr(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + runUnaryNumOpTest(OpType::Ceil, 8.0, 8.0); + runUnaryNumOpTest(OpType::Ceil, 3.2, 4.0); + runUnaryNumOpTest(OpType::Ceil, 3.5, 4.0); + runUnaryNumOpTest(OpType::Ceil, 3.6, 4.0); + runUnaryNumOpTest(OpType::Ceil, 0.4, 1.0); + runUnaryNumOpTest(OpType::Ceil, 0.0, 0.0); + runUnaryNumOpTest(OpType::Ceil, -0.0, -0.0); + runUnaryNumOpTest(OpType::Ceil, -2.4, -2.0); + runUnaryNumOpTest(OpType::Ceil, -2.5, -2.0); + runUnaryNumOpTest(OpType::Ceil, -2.6, -2.0); + runUnaryNumOpTest(OpType::Ceil, -0.4, -0.0); + runUnaryNumOpTest(OpType::Ceil, -0.5, -0.0); + runUnaryNumOpTest(OpType::Ceil, -0.51, -0.0); + runUnaryNumOpTest(OpType::Ceil, inf, inf); + runUnaryNumOpTest(OpType::Ceil, -inf, -inf); + runUnaryNumOpTest(OpType::Ceil, nan, 0); +} - str = Value(v1.toBool() || v2.toBool()).toString() + '\n'; - expected = str + str; +TEST_F(LLVMCodeBuilderTest, Sqrt) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - code = m_builder->finalize(); - ctx = code->createExecutionContext(&m_target); + runUnaryNumOpTest(OpType::Sqrt, 16.0, 4.0); + runUnaryNumOpTest(OpType::Sqrt, 0.04, 0.2); + runUnaryNumOpTest(OpType::Sqrt, 0.0, 0.0); + runUnaryNumOpTest(OpType::Sqrt, -0.0, 0.0); + runUnaryNumOpTest(OpType::Sqrt, -4.0, -nan); // negative NaN shouldn't be a problem + runUnaryNumOpTest(OpType::Sqrt, inf, inf); + runUnaryNumOpTest(OpType::Sqrt, -inf, -nan); + runUnaryNumOpTest(OpType::Sqrt, nan, 0); +} - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "OR: " << quotes1 << v1.toString() << quotes1 << " " << quotes2 << v2.toString() << quotes2; - }; +TEST_F(LLVMCodeBuilderTest, Sin) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(10, 8); - addOpTest(-4.25, -4.25); - addOpTest(-4.25, 5.312); + runUnaryNumOpTest(OpType::Sin, 30.0, 0.5); + runUnaryNumOpTest(OpType::Sin, 90.0, 1.0); + runUnaryNumOpTest(OpType::Sin, 2.8e-9, 0.0); + runUnaryNumOpTest(OpType::Sin, 2.9e-9, 1e-10); + runUnaryNumOpTest(OpType::Sin, 570.0, -0.5); + runUnaryNumOpTest(OpType::Sin, -30.0, -0.5); + runUnaryNumOpTest(OpType::Sin, -90.0, -1.0); + runUnaryNumOpTest(OpType::Sin, 0.0, 0.0); + runUnaryNumOpTest(OpType::Sin, -0.0, 0.0); + runUnaryNumOpTest(OpType::Sin, inf, -nan); // negative NaN shouldn't be a problem + runUnaryNumOpTest(OpType::Sin, -inf, -nan); + runUnaryNumOpTest(OpType::Sin, nan, 0); +} - addOpTest(true, true); - addOpTest(true, false); - addOpTest(false, true); +TEST_F(LLVMCodeBuilderTest, Cos) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(1, true); - addOpTest(1, false); + runUnaryNumOpTest(OpType::Cos, 60.0, 0.5); + runUnaryNumOpTest(OpType::Cos, 90.0, 0.0); + runUnaryNumOpTest(OpType::Cos, 600.0, -0.5); + runUnaryNumOpTest(OpType::Cos, 89.9999999971352, 1e-10); + runUnaryNumOpTest(OpType::Cos, 89.999999999, 0.0); + runUnaryNumOpTest(OpType::Cos, -60.0, 0.5); + runUnaryNumOpTest(OpType::Cos, -90.0, 0.0); + runUnaryNumOpTest(OpType::Cos, 0.0, 1.0); + runUnaryNumOpTest(OpType::Cos, -0.0, 1.0); + runUnaryNumOpTest(OpType::Cos, inf, -nan); // negative NaN shouldn't be a problem + runUnaryNumOpTest(OpType::Cos, -inf, -nan); + runUnaryNumOpTest(OpType::Cos, nan, 1.0); +} - addOpTest("abc", "def"); - addOpTest("true", "true"); - addOpTest("true", "false"); - addOpTest("false", "true"); - addOpTest("false", "false"); +TEST_F(LLVMCodeBuilderTest, Tan) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(5.25, "5.25"); - addOpTest("5.25", 5.25); - addOpTest(0, "1"); - addOpTest("1", 0); - addOpTest(0, "test"); - addOpTest("test", 0); + runUnaryNumOpTest(OpType::Tan, 45.0, 1.0); + runUnaryNumOpTest(OpType::Tan, 90.0, inf); + runUnaryNumOpTest(OpType::Tan, 270.0, -inf); + runUnaryNumOpTest(OpType::Tan, 450.0, inf); + runUnaryNumOpTest(OpType::Tan, -90.0, -inf); + runUnaryNumOpTest(OpType::Tan, -270.0, inf); + runUnaryNumOpTest(OpType::Tan, -450.0, -inf); + runUnaryNumOpTest(OpType::Tan, 180.0, 0.0); + runUnaryNumOpTest(OpType::Tan, -180.0, 0.0); + runUnaryNumOpTest(OpType::Tan, 2.87e-9, 1e-10); + runUnaryNumOpTest(OpType::Tan, 2.8647e-9, 0.0); + runUnaryNumOpTest(OpType::Tan, 0.0, 0.0); + runUnaryNumOpTest(OpType::Tan, -0.0, 0.0); + runUnaryNumOpTest(OpType::Tan, inf, -nan); // negative NaN shouldn't be a problem + runUnaryNumOpTest(OpType::Tan, -inf, -nan); + runUnaryNumOpTest(OpType::Tan, nan, 0.0); +} +TEST_F(LLVMCodeBuilderTest, Asin) +{ static const double inf = std::numeric_limits::infinity(); static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(inf, inf); - addOpTest(-inf, -inf); - addOpTest(nan, nan); - addOpTest(inf, -inf); - addOpTest(-inf, inf); - addOpTest(inf, nan); - addOpTest(nan, inf); - addOpTest(-inf, nan); - addOpTest(nan, -inf); - - addOpTest(true, "true"); - addOpTest("true", true); - addOpTest(false, "false"); - addOpTest("false", false); - addOpTest(false, "true"); - addOpTest("true", false); - addOpTest(true, "false"); - addOpTest("false", true); - addOpTest(true, "TRUE"); - addOpTest("TRUE", true); - addOpTest(false, "FALSE"); - addOpTest("FALSE", false); - - addOpTest(true, "00001"); - addOpTest("00001", true); - addOpTest(true, "00000"); - addOpTest("00000", true); - addOpTest(false, "00000"); - addOpTest("00000", false); - - addOpTest("true", 1); - addOpTest(1, "true"); - addOpTest("true", 0); - addOpTest(0, "true"); - addOpTest("false", 0); - addOpTest(0, "false"); - addOpTest("false", 1); - addOpTest(1, "false"); - - addOpTest("true", "TRUE"); - addOpTest("true", "FALSE"); - addOpTest("false", "FALSE"); - addOpTest("false", "TRUE"); - - addOpTest(true, inf); - addOpTest(inf, true); - addOpTest(true, -inf); - addOpTest(-inf, true); - addOpTest(true, nan); - addOpTest(nan, true); - addOpTest(false, inf); - addOpTest(inf, false); - addOpTest(false, -inf); - addOpTest(-inf, false); - addOpTest(false, nan); - addOpTest(nan, false); - - addOpTest("Infinity", inf); - addOpTest("Infinity", -inf); - addOpTest("Infinity", nan); - addOpTest("infinity", inf); - addOpTest("infinity", -inf); - addOpTest("infinity", nan); - addOpTest("-Infinity", inf); - addOpTest("-Infinity", -inf); - addOpTest("-Infinity", nan); - addOpTest("-infinity", inf); - addOpTest("-infinity", -inf); - addOpTest("-infinity", nan); - addOpTest("NaN", inf); - addOpTest("NaN", -inf); - addOpTest("NaN", nan); - addOpTest("nan", inf); - addOpTest("nan", -inf); - addOpTest("nan", nan); + runUnaryNumOpTest(OpType::Asin, 1.0, 90.0); + runUnaryNumOpTest(OpType::Asin, 0.5, 30.0); + runUnaryNumOpTest(OpType::Asin, 0.0, 0.0); + runUnaryNumOpTest(OpType::Asin, -0.0, 0.0); + runUnaryNumOpTest(OpType::Asin, -0.5, -30.0); + runUnaryNumOpTest(OpType::Asin, -1.0, -90.0); + runUnaryNumOpTest(OpType::Asin, 1.1, nan); + runUnaryNumOpTest(OpType::Asin, -1.2, nan); + runUnaryNumOpTest(OpType::Asin, inf, nan); + runUnaryNumOpTest(OpType::Asin, -inf, nan); + runUnaryNumOpTest(OpType::Asin, nan, 0.0); } -TEST_F(LLVMCodeBuilderTest, Not) +TEST_F(LLVMCodeBuilderTest, Acos) { - auto addOpTest = [this](Value v) { - createBuilder(true); - - m_builder->addConstValue(v); - m_builder->createNot(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - m_builder->addConstValue(v); - callConstFuncForType(v.type()); - m_builder->createNot(); - m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }); + runUnaryNumOpTest(OpType::Acos, 1.0, 0.0); + runUnaryNumOpTest(OpType::Acos, 0.5, 60.0); + runUnaryNumOpTest(OpType::Acos, 0.0, 90.0); + runUnaryNumOpTest(OpType::Acos, -0.0, 90.0); + runUnaryNumOpTest(OpType::Acos, -0.5, 120.0); + runUnaryNumOpTest(OpType::Acos, -1.0, 180.0); + runUnaryNumOpTest(OpType::Acos, 1.1, nan); + runUnaryNumOpTest(OpType::Acos, -1.2, nan); + runUnaryNumOpTest(OpType::Acos, inf, nan); + runUnaryNumOpTest(OpType::Acos, -inf, nan); + runUnaryNumOpTest(OpType::Acos, nan, 90.0); +} - std::string str = Value(!v.toBool()).toString() + '\n'; - std::string expected = str + str; +TEST_F(LLVMCodeBuilderTest, Atan) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + runUnaryNumOpTest(OpType::Atan, 1.0, 45.0); + runUnaryNumOpTest(OpType::Atan, 0.0, 0.0); + runUnaryNumOpTest(OpType::Atan, -0.0, -0.0); + runUnaryNumOpTest(OpType::Atan, -1.0, -45.0); + runUnaryNumOpTest(OpType::Atan, inf, 90.0); + runUnaryNumOpTest(OpType::Atan, -inf, -90.0); + runUnaryNumOpTest(OpType::Atan, nan, 0.0); +} - testing::internal::CaptureStdout(); - code->run(ctx.get()); - const std::string quotes = v.isString() ? "\"" : ""; - ASSERT_THAT(testing::internal::GetCapturedStdout(), Eq(expected)) << "NOT: " << quotes << v.toString() << quotes; - }; +TEST_F(LLVMCodeBuilderTest, Ln) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(10); - addOpTest(-4.25); - addOpTest(5.312); - addOpTest(1); - addOpTest(0); + runUnaryNumOpTest(OpType::Ln, std::exp(1.0), 1.0); + runUnaryNumOpTest(OpType::Ln, std::exp(2.0), 2.0); + runUnaryNumOpTest(OpType::Ln, std::exp(0.3), 0.3); + runUnaryNumOpTest(OpType::Ln, 1.0, 0.0); + runUnaryNumOpTest(OpType::Ln, 0.0, -inf); + runUnaryNumOpTest(OpType::Ln, -0.0, -inf); + runUnaryNumOpTest(OpType::Ln, -0.7, -nan); // negative NaN shouldn't be a problem + runUnaryNumOpTest(OpType::Ln, inf, inf); + runUnaryNumOpTest(OpType::Ln, -inf, -nan); + runUnaryNumOpTest(OpType::Ln, nan, -inf); +} - addOpTest(true); - addOpTest(false); +TEST_F(LLVMCodeBuilderTest, Log10) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest("abc"); - addOpTest("5.25"); - addOpTest("0"); + runUnaryNumOpTest(OpType::Log10, 10.0, 1.0); + runUnaryNumOpTest(OpType::Log10, 1000.0, 3.0); + runUnaryNumOpTest(OpType::Log10, 0.01, -2.0); + runUnaryNumOpTest(OpType::Log10, 0.0, -inf); + runUnaryNumOpTest(OpType::Log10, -0.0, -inf); + runUnaryNumOpTest(OpType::Log10, -0.7, nan); + runUnaryNumOpTest(OpType::Log10, inf, inf); + runUnaryNumOpTest(OpType::Log10, -inf, nan); + runUnaryNumOpTest(OpType::Log10, nan, -inf); +} +TEST_F(LLVMCodeBuilderTest, Exp) +{ static const double inf = std::numeric_limits::infinity(); static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest(inf); - addOpTest(-inf); - addOpTest(nan); - - addOpTest("true"); - addOpTest("false"); - addOpTest("TRUE"); - addOpTest("FALSE"); + runUnaryNumOpTest(OpType::Exp, 1.0, std::exp(1.0)); + runUnaryNumOpTest(OpType::Exp, 0.5, std::exp(0.5)); + runUnaryNumOpTest(OpType::Exp, 0.0, 1.0); + runUnaryNumOpTest(OpType::Exp, -0.0, 1.0); + runUnaryNumOpTest(OpType::Exp, -0.7, std::exp(-0.7)); + runUnaryNumOpTest(OpType::Exp, inf, inf); + runUnaryNumOpTest(OpType::Exp, -inf, 0.0); + runUnaryNumOpTest(OpType::Exp, nan, 1.0); +} - addOpTest("00001"); - addOpTest("00000"); +TEST_F(LLVMCodeBuilderTest, Exp10) +{ + static const double inf = std::numeric_limits::infinity(); + static const double nan = std::numeric_limits::quiet_NaN(); - addOpTest("Infinity"); - addOpTest("infinity"); - addOpTest("-Infinity"); - addOpTest("-infinity"); - addOpTest("NaN"); - addOpTest("nan"); + runUnaryNumOpTest(OpType::Exp10, 1.0, 10.0); + runUnaryNumOpTest(OpType::Exp10, 3.0, 1000.0); + runUnaryNumOpTest(OpType::Exp10, 0.0, 1.0); + runUnaryNumOpTest(OpType::Exp10, -0.0, 1.0); + runUnaryNumOpTest(OpType::Exp10, -1.0, 0.1); + runUnaryNumOpTest(OpType::Exp10, -5.0, 0.00001); + runUnaryNumOpTest(OpType::Exp10, inf, inf); + runUnaryNumOpTest(OpType::Exp10, -inf, 0.0); + runUnaryNumOpTest(OpType::Exp10, nan, 1.0); } -TEST_F(LLVMCodeBuilderTest, Yield) +TEST_F(LLVMCodeBuilderTest, LocalVariables) { - auto build = [this]() { - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); + createBuilder(&sprite, true); - m_builder->yield(); + 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); - m_builder->addConstValue("1"); - m_builder->addFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }); - m_builder->addConstValue("2"); - m_builder->addConstValue(3); - m_builder->addFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }); - - m_builder->addConstValue("test"); - m_builder->addConstValue("4"); - m_builder->addConstValue("5"); - m_builder->addFunctionCall("test_function_3_args_ret", Compiler::StaticType::String, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - }; + CompilerValue *v = m_builder->addConstValue(5); + m_builder->createLocalVariableWrite(var1, v); - // Without warp - createBuilder(false); - build(); + v = m_builder->addConstValue(-23.5); + v = callConstFuncForType(ValueType::Number, v); + m_builder->createLocalVariableWrite(var2, v); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + v = m_builder->addConstValue(5.2); + v = callConstFuncForType(ValueType::Number, v); + m_builder->createLocalVariableWrite(var2, v); - static const std::string expected1 = - "no_args\n" - "no_args_ret\n" - "1_arg no_args_output\n"; - - EXPECT_CALL(m_target, isStage()).Times(3); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); - ASSERT_FALSE(code->isFinished(ctx.get())); + v = m_builder->addConstValue(false); + m_builder->createLocalVariableWrite(var3, v); - static const std::string expected2 = - "1_arg_ret 1\n" - "3_args 1_arg_output 2 3\n" - "3_args test 4 5\n" - "1_arg 3_args_output\n"; + v = m_builder->addConstValue(true); + m_builder->createLocalVariableWrite(var3, v); - EXPECT_CALL(m_target, isStage()).Times(4); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_EQ(testing::internal::GetCapturedStdout(), expected2); - ASSERT_TRUE(code->isFinished(ctx.get())); + v = m_builder->addConstValue(false); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->createLocalVariableWrite(var4, v); - // With warp - createBuilder(true); - build(); - code = m_builder->finalize(); - ctx = code->createExecutionContext(&m_target); + 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 = - "no_args\n" - "no_args_ret\n" - "1_arg no_args_output\n" - "1_arg_ret 1\n" - "3_args 1_arg_output 2 3\n" - "3_args test 4 5\n" - "1_arg 3_args_output\n"; + "5\n" + "5.2\n" + "true\n" + "false\n"; - EXPECT_CALL(m_target, isStage()).Times(7); + 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, IfStatement) +TEST_F(LLVMCodeBuilderTest, WriteVariable) { - createBuilder(true); - - // Without else branch (const condition) - m_builder->addConstValue("true"); - m_builder->beginIfStatement(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endIf(); + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - m_builder->addConstValue("false"); - m_builder->beginIfStatement(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endIf(); + auto globalVar1 = std::make_shared("", ""); + auto globalVar2 = std::make_shared("", ""); + auto globalVar3 = std::make_shared("", ""); + stage.addVariable(globalVar1); + stage.addVariable(globalVar2); + stage.addVariable(globalVar3); - // Without else branch (condition returned by function) - m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}); - m_builder->addConstValue("no_args_output"); - m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }); - m_builder->beginIfStatement(); - m_builder->addConstValue(0); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endIf(); + auto localVar1 = std::make_shared("", ""); + auto localVar2 = std::make_shared("", ""); + auto localVar3 = std::make_shared("", ""); + sprite.addVariable(localVar1); + sprite.addVariable(localVar2); + sprite.addVariable(localVar3); - m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}); - m_builder->addConstValue(""); - m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }); - m_builder->beginIfStatement(); - m_builder->addConstValue(1); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endIf(); + createBuilder(&sprite, true); - // With else branch (const condition) - m_builder->addConstValue("true"); - m_builder->beginIfStatement(); - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->beginElseBranch(); - m_builder->addConstValue(3); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endIf(); + CompilerValue *v = m_builder->addConstValue(5); + m_builder->createVariableWrite(globalVar1.get(), v); - m_builder->addConstValue("false"); - m_builder->beginIfStatement(); - m_builder->addConstValue(4); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->beginElseBranch(); - m_builder->addConstValue(5); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endIf(); + v = m_builder->addConstValue(-23.5); + v = callConstFuncForType(ValueType::Number, v); + m_builder->createVariableWrite(globalVar2.get(), v); - // With else branch (condition returned by function) - m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}); - m_builder->addConstValue("no_args_output"); - m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }); - m_builder->beginIfStatement(); - m_builder->addConstValue(6); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->beginElseBranch(); - m_builder->addConstValue(7); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endIf(); + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar3.get(), v); - m_builder->addFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}); - m_builder->addConstValue(""); - m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }); - m_builder->beginIfStatement(); - m_builder->addConstValue(8); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->beginElseBranch(); - m_builder->addConstValue(9); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endIf(); + v = m_builder->addConstValue("abc"); + m_builder->createVariableWrite(localVar1.get(), v); - // 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", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->beginElseBranch(); - { - m_builder->addConstValue(1); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addConstValue("hello world"); + v = callConstFuncForType(ValueType::String, v); + m_builder->createVariableWrite(localVar1.get(), v); - m_builder->addConstValue(false); - m_builder->beginIfStatement(); - m_builder->beginElseBranch(); - { - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - 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", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->beginElseBranch(); - { - m_builder->addConstValue(4); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->endIf(); - } - m_builder->endIf(); + v = m_builder->addConstValue(false); + m_builder->createVariableWrite(localVar2.get(), v); - // 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", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->beginElseBranch(); - { - m_builder->addConstValue(6); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->endIf(); - } - m_builder->beginElseBranch(); - { - m_builder->addConstValue(true); - m_builder->beginIfStatement(); - { - m_builder->addConstValue(7); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->beginElseBranch(); - m_builder->endIf(); - } - m_builder->endIf(); + v = m_builder->addConstValue(true); + v = callConstFuncForType(ValueType::Bool, v); + m_builder->createVariableWrite(localVar3.get(), v); auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); + + ASSERT_EQ(globalVar1->value(), 5); + ASSERT_EQ(globalVar2->value(), -23.5); + ASSERT_EQ(globalVar3->value(), "test"); + ASSERT_EQ(localVar1->value(), "hello world"); + ASSERT_EQ(localVar2->value(), false); + ASSERT_EQ(localVar3->value(), true); +} + +TEST_F(LLVMCodeBuilderTest, Select) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + createBuilder(&sprite, true); + + // Number + CompilerValue *v = m_builder->addConstValue(true); + v = m_builder->createSelect(v, m_builder->addConstValue(5.8), m_builder->addConstValue(-17.42), Compiler::StaticType::Number); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(false); + v = m_builder->createSelect(v, m_builder->addConstValue(5.8), m_builder->addConstValue(-17.42), Compiler::StaticType::Number); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + // Bool + v = m_builder->addConstValue(true); + v = m_builder->createSelect(v, m_builder->addConstValue(true), m_builder->addConstValue(false), Compiler::StaticType::Bool); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(false); + v = m_builder->createSelect(v, m_builder->addConstValue(true), m_builder->addConstValue(false), Compiler::StaticType::Bool); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + // String + v = m_builder->addConstValue(true); + v = m_builder->createSelect(v, m_builder->addConstValue("hello"), m_builder->addConstValue("world"), Compiler::StaticType::String); + 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("hello"), m_builder->addConstValue("world"), Compiler::StaticType::String); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + // Different types + v = m_builder->addConstValue(true); + v = m_builder->createSelect(v, m_builder->addConstValue("543"), m_builder->addConstValue("true"), Compiler::StaticType::Number); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(false); + v = m_builder->createSelect(v, m_builder->addConstValue("543"), m_builder->addConstValue("true"), Compiler::StaticType::Number); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(true); + 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 }); + + v = m_builder->addConstValue(false); + 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 = - "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"; + "5.8\n" + "-17.42\n" + "1\n" + "0\n" + "hello\n" + "world\n" + "543\n" + "0\n" + "1\n" + "0\n" + "test\n" + "true\n"; - EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + 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, RepeatLoop) +TEST_F(LLVMCodeBuilderTest, ReadVariable) { - createBuilder(true); + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - // Const count - m_builder->addConstValue("-5"); - m_builder->beginRepeatLoop(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endLoop(); - - m_builder->addConstValue(0); - m_builder->beginRepeatLoop(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + auto globalVar1 = std::make_shared("", "", 87); + auto globalVar2 = std::make_shared("", "", 6.4); + auto globalVar3 = std::make_shared("", "", "abc"); + stage.addVariable(globalVar1); + stage.addVariable(globalVar2); + stage.addVariable(globalVar3); - m_builder->addConstValue(3); - m_builder->beginRepeatLoop(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + auto localVar1 = std::make_shared("", "", false); + auto localVar2 = std::make_shared("", "", true); + auto localVar3 = std::make_shared("", "", "test"); + sprite.addVariable(localVar1); + sprite.addVariable(localVar2); + sprite.addVariable(localVar3); - m_builder->addConstValue("2.4"); - m_builder->beginRepeatLoop(); - m_builder->addConstValue(0); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endLoop(); + createBuilder(&sprite, true); - m_builder->addConstValue("2.5"); - m_builder->beginRepeatLoop(); - m_builder->addConstValue(1); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->endLoop(); + CompilerValue *v = m_builder->addVariableValue(globalVar1.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - // Count returned by function - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_const_number", Compiler::StaticType::Number, { Compiler::StaticType::Number }); - m_builder->beginRepeatLoop(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + v = m_builder->addVariableValue(globalVar2.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - // 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", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->endLoop(); + v = m_builder->addVariableValue(globalVar3.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); + v = m_builder->addVariableValue(localVar1.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - m_builder->addConstValue(3); - m_builder->beginRepeatLoop(); - { - m_builder->addConstValue(3); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - } - m_builder->endLoop(); - } - m_builder->endLoop(); + v = m_builder->addVariableValue(localVar2.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + v = m_builder->addVariableValue(localVar3.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); - static const std::string expected = - "no_args\n" - "no_args\n" - "no_args\n" - "1_arg 0\n" - "1_arg 0\n" - "1_arg 1\n" - "1_arg 1\n" - "1_arg 1\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"; + std::string expected; + expected += globalVar1->value().toString() + '\n'; + expected += globalVar2->value().toString() + '\n'; + expected += globalVar3->value().toString() + '\n'; + expected += localVar1->value().toString() + '\n'; + expected += localVar2->value().toString() + '\n'; + expected += localVar3->value().toString() + '\n'; - EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + 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); +} - // Yield - createBuilder(false); +TEST_F(LLVMCodeBuilderTest, ClearList) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList1 = std::make_shared("", ""); + auto globalList2 = std::make_shared("", ""); + auto globalList3 = std::make_shared("", ""); + stage.addList(globalList1); + stage.addList(globalList2); + stage.addList(globalList3); + + auto localList1 = std::make_shared("", ""); + auto localList2 = std::make_shared("", ""); + auto localList3 = std::make_shared("", ""); + sprite.addList(localList1); + sprite.addList(localList2); + sprite.addList(localList3); + + globalList1->append(1); + globalList1->append(2); + globalList1->append(3); + strings[globalList1.get()] = globalList1->toString(); + + globalList2->append("Lorem"); + globalList2->append("ipsum"); + globalList2->append(-4.52); + strings[globalList2.get()] = globalList2->toString(); + + globalList3->append(true); + globalList3->append(false); + globalList3->append(true); + strings[globalList3.get()] = globalList3->toString(); + + localList1->append("dolor"); + localList1->append("sit"); + localList1->append("amet"); + strings[localList1.get()] = localList1->toString(); + + localList2->append(10); + localList2->append(9.8); + localList2->append(true); + strings[localList2.get()] = localList2->toString(); + + localList3->append("test"); + localList3->append(1.2); + localList3->append(false); + strings[localList3.get()] = localList3->toString(); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList1.get()); + m_builder->createListClear(globalList3.get()); + m_builder->createListClear(localList1.get()); + m_builder->createListClear(localList2.get()); - m_builder->addConstValue(3); - m_builder->beginRepeatLoop(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); - code = m_builder->finalize(); - ctx = code->createExecutionContext(&m_target); + ASSERT_TRUE(globalList1->empty()); + ASSERT_EQ(globalList2->toString(), strings[globalList2.get()]); + ASSERT_TRUE(globalList3->empty()); - static const std::string expected1 = "no_args\n"; + ASSERT_TRUE(localList1->empty()); + ASSERT_TRUE(localList2->empty()); + ASSERT_EQ(localList3->toString(), strings[localList3.get()]); +} - EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); +TEST_F(LLVMCodeBuilderTest, RemoveFromList) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - for (int i = 0; i < 3; i++) { - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); - ASSERT_FALSE(code->isFinished(ctx.get())); - } + std::unordered_map strings; - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_TRUE(testing::internal::GetCapturedStdout().empty()); - ASSERT_TRUE(code->isFinished(ctx.get())); + auto globalList = std::make_shared("", ""); + stage.addList(globalList); - // No warp no-op loop - createBuilder(false); + auto localList = std::make_shared("", ""); + sprite.addList(localList); - m_builder->addConstValue(0); // don't yield - m_builder->beginRepeatLoop(); - m_builder->endLoop(); + globalList->append(1); + globalList->append(2); + globalList->append(3); - code = m_builder->finalize(); - ctx = code->createExecutionContext(&m_target); + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(1); + m_builder->createListRemove(globalList.get(), v); + + v = m_builder->addConstValue(-1); + m_builder->createListRemove(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->createListRemove(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->createListRemove(localList.get(), v); + + v = m_builder->addConstValue(-1); + m_builder->createListRemove(localList.get(), v); + + v = m_builder->addConstValue(4); + m_builder->createListRemove(localList.get(), v); + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); code->run(ctx.get()); - ASSERT_TRUE(code->isFinished(ctx.get())); + + ASSERT_EQ(globalList->toString(), "13"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor"); } -TEST_F(LLVMCodeBuilderTest, WhileLoop) +TEST_F(LLVMCodeBuilderTest, AppendToList) { - createBuilder(true); + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - // Const condition - m_builder->beginLoopCondition(); - m_builder->addConstValue("false"); - m_builder->beginWhileLoop(); - m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + std::unordered_map strings; - m_builder->beginLoopCondition(); - m_builder->addConstValue(false); - m_builder->beginWhileLoop(); - m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + auto globalList = std::make_shared("", ""); + stage.addList(globalList); - // Condition returned by function - m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}); - m_builder->beginLoopCondition(); - m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}); - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }); - m_builder->beginWhileLoop(); - m_builder->addConstValue(0); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + auto localList = std::make_shared("", ""); + sprite.addList(localList); - // Nested - m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}); - m_builder->beginLoopCondition(); - m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}); - m_builder->addConstValue(3); - m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }); - m_builder->beginWhileLoop(); - { - m_builder->beginLoopCondition(); - m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}); - m_builder->addConstValue(3); - m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }); - m_builder->beginWhileLoop(); - { - m_builder->addConstValue(1); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}); - } - m_builder->endLoop(); + globalList->append(1); + globalList->append(2); + globalList->append(3); - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); - m_builder->beginLoopCondition(); - m_builder->addConstValue(false); - m_builder->beginWhileLoop(); - { - m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}); - } - m_builder->endLoop(); - } - m_builder->endLoop(); + createBuilder(&sprite, true); - auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + CompilerValue *v = m_builder->addConstValue(1); + m_builder->createListAppend(globalList.get(), v); - 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"; + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); - EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + v = m_builder->addConstValue(3); + m_builder->createListAppend(localList.get(), v); - // Yield - createBuilder(false); + m_builder->createListClear(localList.get()); - m_builder->beginLoopCondition(); - m_builder->addConstValue(true); - m_builder->beginWhileLoop(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + v = m_builder->addConstValue(true); + m_builder->createListAppend(localList.get(), v); - code = m_builder->finalize(); - ctx = code->createExecutionContext(&m_target); + v = m_builder->addConstValue(false); + m_builder->createListAppend(localList.get(), v); - static const std::string expected1 = "no_args\n"; + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(localList.get(), v); - EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); - for (int i = 0; i < 10; i++) { - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); - ASSERT_FALSE(code->isFinished(ctx.get())); - } + ASSERT_EQ(globalList->toString(), "1 2 3 1 test"); + ASSERT_EQ(localList->toString(), "true false hello world"); } -TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop) +TEST_F(LLVMCodeBuilderTest, InsertToList) { - createBuilder(true); + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - // Const condition - m_builder->beginLoopCondition(); - m_builder->addConstValue("true"); - m_builder->beginRepeatUntilLoop(); - m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + std::unordered_map strings; - m_builder->beginLoopCondition(); - m_builder->addConstValue(true); - m_builder->beginRepeatUntilLoop(); - m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}); - m_builder->endLoop(); + auto globalList = std::make_shared("", ""); + stage.addList(globalList); - // Condition returned by function - m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}); - m_builder->beginLoopCondition(); - m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}); - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }); - m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->beginRepeatUntilLoop(); - m_builder->addConstValue(0); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}); + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + CompilerValue *v1 = m_builder->addConstValue(2); + CompilerValue *v2 = m_builder->addConstValue(1); + m_builder->createListInsert(globalList.get(), v1, v2); + + v1 = m_builder->addConstValue(3); + v2 = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), v1, v2); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(3); + m_builder->createListInsert(localList.get(), v1, v2); + + m_builder->createListClear(localList.get()); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(true); + m_builder->createListInsert(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(false); + m_builder->createListInsert(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue("hello world"); + m_builder->createListInsert(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(3); + v2 = m_builder->addConstValue("test"); + m_builder->createListInsert(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(-1); + v2 = m_builder->addConstValue(123); + m_builder->createListInsert(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(6); + v2 = m_builder->addConstValue(123); + m_builder->createListInsert(localList.get(), v1, v2); + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); + + ASSERT_EQ(globalList->toString(), "1 2 1 test 3"); + ASSERT_EQ(localList->toString(), "false hello world true test"); +} + +TEST_F(LLVMCodeBuilderTest, ListReplace) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + CompilerValue *v1 = m_builder->addConstValue(2); + CompilerValue *v2 = m_builder->addConstValue(1); + m_builder->createListReplace(globalList.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get(), v1, v2); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(3); + m_builder->createListReplace(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(2); + v2 = m_builder->addConstValue(true); + m_builder->createListReplace(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(3); + v2 = m_builder->addConstValue("hello world"); + m_builder->createListReplace(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(-1); + v2 = m_builder->addConstValue(123); + m_builder->createListReplace(localList.get(), v1, v2); + + v1 = m_builder->addConstValue(5); + v2 = m_builder->addConstValue(123); + m_builder->createListReplace(localList.get(), v1, v2); + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); + + ASSERT_EQ(globalList->toString(), "1 test 1"); + ASSERT_EQ(localList->toString(), "3 ipsum true hello world"); +} + +TEST_F(LLVMCodeBuilderTest, GetListContents) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + std::unordered_map strings; + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + strings[localList.get()] = localList->toString(); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addListContents(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListContents(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + static const std::string expected = + "123\n" + "Lorem ipsum dolor sit\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); + + ASSERT_EQ(globalList->toString(), "123"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + +TEST_F(LLVMCodeBuilderTest, GetListItem) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList1 = std::make_shared("", ""); + sprite.addList(localList1); + + auto localList2 = std::make_shared("", ""); + sprite.addList(localList2); + + auto localList3 = std::make_shared("", ""); + sprite.addList(localList3); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList1->append("Lorem"); + localList1->append("ipsum"); + localList1->append("dolor"); + localList1->append("sit"); + + localList2->append(-564.121); + localList2->append(4257.4); + + localList3->append(true); + localList3->append(false); + + createBuilder(&sprite, true); + + // Global + CompilerValue *v = m_builder->addConstValue(2); + v = m_builder->addListItem(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + CompilerValue *v1 = m_builder->addConstValue(1); + CompilerValue *v2 = m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get(), v1, v2); + + v = m_builder->addConstValue(0); + v = m_builder->addListItem(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(-1); + v = m_builder->addListItem(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(3); + v = m_builder->addListItem(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + // Local 1 + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList1.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(2); + v = m_builder->addListItem(localList1.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(3); + v = m_builder->addListItem(localList1.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(-1); + v = m_builder->addListItem(localList1.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(4); + v = m_builder->addListItem(localList1.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + // Local 2 + v = m_builder->addConstValue(-1); + v = m_builder->addListItem(localList2.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + v = m_builder->addListItem(localList2.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + // Local 3 + v = m_builder->addConstValue(-1); + v = m_builder->addListItem(localList3.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + v = m_builder->addListItem(localList3.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + static const std::string expected = + "3\n" + "1\n" + "0\n" + "0\n" + "Lorem\n" + "dolor\n" + "sit\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\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); + + ASSERT_EQ(globalList->toString(), "1 test 3"); + ASSERT_EQ(localList1->toString(), "Lorem ipsum dolor sit"); + ASSERT_EQ(localList2->toString(), "-564.121 4257.4"); + ASSERT_EQ(localList3->toString(), "true false"); +} + +TEST_F(LLVMCodeBuilderTest, GetListSize) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addListSize(globalList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListSize(localList.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + static const std::string expected = + "3\n" + "4\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); + + ASSERT_EQ(globalList->toString(), "123"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + +TEST_F(LLVMCodeBuilderTest, GetListItemIndex) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + v = m_builder->addListItemIndex(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(1); + v = m_builder->addListItemIndex(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + v = m_builder->addListItemIndex(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + CompilerValue *v1 = m_builder->addConstValue(1); + CompilerValue *v2 = m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get(), v1, v2); + + v = m_builder->addConstValue(2); + v = m_builder->addListItemIndex(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(1); + v = m_builder->addListItemIndex(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("test"); + v = m_builder->addListItemIndex(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("abc"); + v = m_builder->addListItemIndex(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("doLor"); + v = m_builder->addListItemIndex(localList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(true); + v = m_builder->addListItemIndex(localList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("site"); + v = m_builder->addListItemIndex(localList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + static const std::string expected = + "1\n" + "0\n" + "-1\n" + "-1\n" + "0\n" + "1\n" + "-1\n" + "2\n" + "-1\n" + "-1\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); + + ASSERT_EQ(globalList->toString(), "1 test 3"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + +TEST_F(LLVMCodeBuilderTest, ListContainsItem) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + globalList->append(1); + globalList->append(2); + globalList->append(3); + + localList->append("Lorem"); + localList->append("ipsum"); + localList->append("dolor"); + localList->append("sit"); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + v = m_builder->addListContains(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(1); + v = m_builder->addListContains(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + v = m_builder->addListContains(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + CompilerValue *v1 = m_builder->addConstValue(1); + CompilerValue *v2 = m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get(), v1, v2); + + v = m_builder->addConstValue(2); + v = m_builder->addListContains(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(1); + v = m_builder->addListContains(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("test"); + v = m_builder->addListContains(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("abc"); + v = m_builder->addListContains(globalList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("doLor"); + v = m_builder->addListContains(localList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(true); + v = m_builder->addListContains(localList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue("site"); + v = m_builder->addListContains(localList.get(), v); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + static const std::string expected = + "true\n" + "true\n" + "false\n" + "false\n" + "true\n" + "true\n" + "false\n" + "true\n" + "false\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); + + ASSERT_EQ(globalList->toString(), "1 test 3"); + ASSERT_EQ(localList->toString(), "Lorem ipsum dolor sit"); +} + +TEST_F(LLVMCodeBuilderTest, Yield) +{ + auto build = [this]() { + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + + CompilerValue *v = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {}); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + m_builder->yield(); + + v = m_builder->addConstValue("1"); + v = m_builder->addTargetFunctionCall("test_function_1_arg_ret", Compiler::StaticType::String, { Compiler::StaticType::String }, { v }); + CompilerValue *v1 = m_builder->addConstValue("2"); + CompilerValue *v2 = m_builder->addConstValue(3); + m_builder + ->addTargetFunctionCall("test_function_3_args", Compiler::StaticType::Void, { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, { v, v1, v2 }); + + v = m_builder->addConstValue("test"); + v1 = m_builder->addConstValue("4"); + v2 = m_builder->addConstValue("5"); + v = m_builder->addTargetFunctionCall( + "test_function_3_args_ret", + Compiler::StaticType::String, + { Compiler::StaticType::String, Compiler::StaticType::String, Compiler::StaticType::String }, + { v, v1, v2 }); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + }; + + // Without warp + createBuilder(false); + build(); + + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + static const std::string expected1 = + "no_args\n" + "no_args_ret\n" + "1_arg no_args_output\n"; + + EXPECT_CALL(m_target, isStage()).Times(3); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); + ASSERT_FALSE(code->isFinished(ctx.get())); + + static const std::string expected2 = + "1_arg_ret 1\n" + "3_args 1_arg_output 2 3\n" + "3_args test 4 5\n" + "1_arg 3_args_output\n"; + + EXPECT_CALL(m_target, isStage()).Times(4); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected2); + ASSERT_TRUE(code->isFinished(ctx.get())); + + // With warp + createBuilder(true); + build(); + code = m_builder->finalize(); + ctx = code->createExecutionContext(&thread); + + static const std::string expected = + "no_args\n" + "no_args_ret\n" + "1_arg no_args_output\n" + "1_arg_ret 1\n" + "3_args 1_arg_output 2 3\n" + "3_args test 4 5\n" + "1_arg 3_args_output\n"; + + EXPECT_CALL(m_target, isStage()).Times(7); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, VariablesAfterSuspend) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", 87); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "test"); + sprite.addVariable(localVar); + + createBuilder(&sprite, false); + + CompilerValue *v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(true); + m_builder->createVariableWrite(localVar.get(), v); + + m_builder->yield(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + + m_builder->beginLoopCondition(); + v = m_builder->createCmpLT(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(3)); + m_builder->beginWhileLoop(v); + m_builder->endLoop(); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + + m_builder->beginLoopCondition(); + v = m_builder->createCmpEQ(m_builder->addVariableValue(localVar.get()), m_builder->addConstValue(2)); + m_builder->beginRepeatUntilLoop(v); + m_builder->endLoop(); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + std::string expected = + "hello world\n" + "-4.8\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue("hello world"); + localVar->setValue(-4.8); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue("test"); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalVar->setValue(true); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "true\n"); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(1); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue("2"); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(3); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "3\n"); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(1); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), ""); + ASSERT_FALSE(code->isFinished(ctx.get())); + + localVar->setValue(2); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "2\n"); +} + +TEST_F(LLVMCodeBuilderTest, ListsAfterSuspend) +{ + + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList1 = std::make_shared("", ""); + stage.addList(globalList1); + + auto globalList2 = std::make_shared("", ""); + stage.addList(globalList2); + + auto localList1 = std::make_shared("", ""); + sprite.addList(localList1); + + auto localList2 = std::make_shared("", ""); + sprite.addList(localList2); + + createBuilder(&sprite, false); + + m_builder->createListClear(globalList1.get()); + m_builder->createListClear(globalList2.get()); + m_builder->createListClear(localList1.get()); + m_builder->createListClear(localList2.get()); + + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1)); + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2)); + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(3)); + + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue(1)); + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue(2)); + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue(3)); + + m_builder->createListReplace(globalList2.get(), m_builder->addConstValue(1), m_builder->addConstValue(12.5)); + + m_builder->createListInsert(localList1.get(), m_builder->addConstValue(0), m_builder->addConstValue("Lorem")); + m_builder->createListInsert(localList1.get(), m_builder->addConstValue(0), m_builder->addConstValue("ipsum")); + + m_builder->createListInsert(localList2.get(), m_builder->addConstValue(0), m_builder->addConstValue(true)); + m_builder->createListInsert(localList2.get(), m_builder->addConstValue(0), m_builder->addConstValue(false)); + + m_builder->yield(); + + CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(1)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(2)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(localList1.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(localList1.get(), m_builder->addConstValue(1)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(localList2.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + std::string expected = + "2\n" + "test\n" + "12.5\n" + "false\n" + "ipsum\n" + "-5.48\n" + "hello\n"; + + auto code = m_builder->finalize(); + Script script(&sprite, nullptr, nullptr); + script.setCode(code); + ; + Thread thread(&sprite, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); + ASSERT_FALSE(code->isFinished(ctx.get())); + + globalList1->replace(2, "test"); + globalList2->replace(0, false); + localList1->replace(1, -5.48); + localList2->replace(0, "hello"); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); +} + +TEST_F(LLVMCodeBuilderTest, IfStatement) +{ + createBuilder(true); + + // Without else branch (const condition) + CompilerValue *v = m_builder->addConstValue("true"); + m_builder->beginIfStatement(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endIf(); + + v = m_builder->addConstValue("false"); + m_builder->beginIfStatement(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endIf(); + + // Without else branch (condition returned by function) + CompilerValue *v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {}); + CompilerValue *v2 = m_builder->addConstValue("no_args_output"); + v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 }); + m_builder->beginIfStatement(v); + v = m_builder->addConstValue(0); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->endIf(); + + v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {}); + v2 = m_builder->addConstValue(""); + v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 }); + m_builder->beginIfStatement(v); + v = m_builder->addConstValue(1); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->endIf(); + + // With else branch (const condition) + v = m_builder->addConstValue("true"); + m_builder->beginIfStatement(v); + v = m_builder->addConstValue(2); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->beginElseBranch(); + v = m_builder->addConstValue(3); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->endIf(); + + v = m_builder->addConstValue("false"); + m_builder->beginIfStatement(v); + v = m_builder->addConstValue(4); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->beginElseBranch(); + v = m_builder->addConstValue(5); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->endIf(); + + // With else branch (condition returned by function) + v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {}); + v2 = m_builder->addConstValue("no_args_output"); + v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 }); + m_builder->beginIfStatement(v); + v = m_builder->addConstValue(6); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->beginElseBranch(); + v = m_builder->addConstValue(7); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->endIf(); + + v1 = m_builder->addTargetFunctionCall("test_function_no_args_ret", Compiler::StaticType::String, {}, {}); + v2 = m_builder->addConstValue(""); + v = m_builder->addFunctionCall("test_equals", Compiler::StaticType::Bool, { Compiler::StaticType::String, Compiler::StaticType::String }, { v1, v2 }); + m_builder->beginIfStatement(v); + v = m_builder->addConstValue(8); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->beginElseBranch(); + v = m_builder->addConstValue(9); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + 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); + { + v = m_builder->addConstValue(false); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue(0); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + } + m_builder->beginElseBranch(); + { + 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(); + { + v = m_builder->addConstValue(2); + 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(); + } + m_builder->beginElseBranch(); + { + v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue(3); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + } + m_builder->beginElseBranch(); + { + v = m_builder->addConstValue(4); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + } + m_builder->endIf(); + } + 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); + { + v = m_builder->addConstValue(false); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue(5); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + } + m_builder->beginElseBranch(); + { + v = m_builder->addConstValue(6); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + } + m_builder->endIf(); + } + 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); + { + v = m_builder->addConstValue(7); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + } + 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(); + + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + 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" + "test\n" + "1_arg 2\n" + "test\n" + "test\n" + "1_arg 7\n" + "test\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, IfStatementVariables) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", "test"); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", 87); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(true); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endIf(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(false); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endIf(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue(true); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endIf(); + + v = m_builder->addConstValue(false); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue(-8.2); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endIf(); + } + m_builder->endIf(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + std::string expected = + "hello world\n" + "12.5\n" + "true\n" + "true\n" + "true\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, IfStatementLists) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList1 = std::make_shared("", ""); + stage.addList(globalList1); + + auto globalList2 = std::make_shared("", ""); + stage.addList(globalList2); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, false); + + auto resetLists = [this, globalList1, globalList2, localList]() { + m_builder->createListClear(globalList1.get()); + m_builder->createListClear(globalList2.get()); + m_builder->createListClear(localList.get()); + + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1)); + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2)); + + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("hello")); + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("world")); + + m_builder->createListAppend(localList.get(), m_builder->addConstValue(false)); + m_builder->createListAppend(localList.get(), m_builder->addConstValue(true)); + }; + + auto checkLists = [this, globalList1, globalList2, localList]() { + CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + }; + + // if (true) + resetLists(); + + CompilerValue *v, *v1, *v2; + v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endIf(); + + checkLists(); + + // if (false) + resetLists(); + + v = m_builder->addConstValue(false); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endIf(); + + checkLists(); + + // if (true) { if (true) { ... }; if (false) { ... } } + resetLists(); + + v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + } + m_builder->endIf(); + + v = m_builder->addConstValue(false); + m_builder->beginIfStatement(v); + { + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endIf(); + } + m_builder->endIf(); + + checkLists(); + + std::string expected = + "1\n" + "hello\n" + "false\n" + "1\n" + "world\n" + "false\n" + "1\n" + "hello\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, RepeatLoop) +{ + createBuilder(true); + + // Const count + CompilerValue *v = m_builder->addConstValue("-5"); + m_builder->beginRepeatLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + v = m_builder->addConstValue(0); + m_builder->beginRepeatLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + v = m_builder->addConstValue("2.4"); + m_builder->beginRepeatLoop(v); + v = m_builder->addConstValue(0); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->endLoop(); + + v = m_builder->addConstValue("2.5"); + m_builder->beginRepeatLoop(v); + v = m_builder->addConstValue(1); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->endLoop(); + + // Count returned by function + v = m_builder->addConstValue(2); + v = callConstFuncForType(ValueType::Number, v); + m_builder->beginRepeatLoop(v); + CompilerValue *index = m_builder->addLoopIndex(); + m_builder->addTargetFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { index }); + 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); + { + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + 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(); + + v = m_builder->addConstValue(2); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + index = m_builder->addLoopIndex(); + m_builder->addTargetFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { index }); + } + m_builder->endLoop(); + } + 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); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + static const std::string expected = + "no_args\n" + "no_args\n" + "no_args\n" + "1_arg 0\n" + "1_arg 0\n" + "1_arg 1\n" + "1_arg 1\n" + "1_arg 1\n" + "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" + "test\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected); + + // Yield + createBuilder(false); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + code = m_builder->finalize(); + ctx = code->createExecutionContext(&thread); + + static const std::string expected1 = "no_args\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + + for (int i = 0; i < 3; i++) { + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); + ASSERT_FALSE(code->isFinished(ctx.get())); + } + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_TRUE(testing::internal::GetCapturedStdout().empty()); + ASSERT_TRUE(code->isFinished(ctx.get())); + + // No warp no-op loop + createBuilder(false); + + v = m_builder->addConstValue(0); // don't yield + m_builder->beginRepeatLoop(v); + m_builder->endLoop(); + + code = m_builder->finalize(); + ctx = code->createExecutionContext(&thread); + code->run(ctx.get()); + ASSERT_TRUE(code->isFinished(ctx.get())); + + // Infinite no warp loop + createBuilder(false); + + v = m_builder->addConstValue("Infinity"); + m_builder->beginRepeatLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + code = m_builder->finalize(); + ctx = code->createExecutionContext(&thread); + + for (int i = 0; i < 10; i++) { + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "no_args\n"); + ASSERT_FALSE(code->isFinished(ctx.get())); + } +} + +TEST_F(LLVMCodeBuilderTest, WhileLoop) +{ + createBuilder(true); + + // Const condition + m_builder->beginLoopCondition(); + CompilerValue *v = m_builder->addConstValue("false"); + m_builder->beginWhileLoop(v); + m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(false); + m_builder->beginWhileLoop(v); + m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + // Condition returned by function + m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {}); + m_builder->beginLoopCondition(); + CompilerValue *v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {}); + CompilerValue *v2 = m_builder->addConstValue(2); + v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 }); + m_builder->beginWhileLoop(v); + v = m_builder->addConstValue(0); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + // Nested + m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {}); + m_builder->beginLoopCondition(); + v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {}); + v2 = m_builder->addConstValue(3); + v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 }); + m_builder->beginWhileLoop(v); + { + m_builder->beginLoopCondition(); + v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {}); + v2 = m_builder->addConstValue(3); + v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 }); + m_builder->beginWhileLoop(v); + { + v = m_builder->addConstValue(1); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {}); + } + m_builder->endLoop(); + + v = m_builder->addConstValue(2); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(false); + m_builder->beginWhileLoop(v); + { + m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {}); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + 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); + + // Yield + createBuilder(false); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(true); + m_builder->beginWhileLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + code = m_builder->finalize(); + ctx = code->createExecutionContext(&thread); + + static const std::string expected1 = "no_args\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + + for (int i = 0; i < 10; i++) { + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); + ASSERT_FALSE(code->isFinished(ctx.get())); + } +} + +TEST_F(LLVMCodeBuilderTest, RepeatUntilLoop) +{ + createBuilder(true); + + // Const condition + m_builder->beginLoopCondition(); + CompilerValue *v = m_builder->addConstValue("true"); + m_builder->beginRepeatUntilLoop(v); + m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(v); + m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + // Condition returned by function + m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {}); + m_builder->beginLoopCondition(); + CompilerValue *v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {}); + CompilerValue *v2 = m_builder->addConstValue(2); + v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 }); + v = m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v }); + m_builder->beginRepeatUntilLoop(v); + v = m_builder->addConstValue(0); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + // Nested + m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}, {}); + m_builder->beginLoopCondition(); + v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {}); + v2 = m_builder->addConstValue(3); + v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 }); + v = m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v }); + m_builder->beginRepeatUntilLoop(v); + { + m_builder->beginLoopCondition(); + v1 = m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}, {}); + v2 = m_builder->addConstValue(3); + v = m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }, { v1, v2 }); + v = m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }, { v }); + m_builder->beginRepeatUntilLoop(v); + { + v = m_builder->addConstValue(1); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}, {}); + } + m_builder->endLoop(); + + v = m_builder->addConstValue(2); + m_builder->addTargetFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(v); + { + m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}, {}); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + auto code = m_builder->finalize(); + Script script(&m_target, nullptr, nullptr); + script.setCode(code); + Thread thread(&m_target, nullptr, &script); + auto ctx = code->createExecutionContext(&thread); + + 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); + + // Yield + createBuilder(false); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(false); + m_builder->beginRepeatUntilLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + code = m_builder->finalize(); + ctx = code->createExecutionContext(&thread); + + static const std::string expected1 = "no_args\n"; + + EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + + for (int i = 0; i < 10; i++) { + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); + ASSERT_FALSE(code->isFinished(ctx.get())); + } +} + +TEST_F(LLVMCodeBuilderTest, LoopVariables) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", "test"); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", 87); + auto counter1 = std::make_shared("", ""); + auto counter2 = std::make_shared("", ""); + sprite.addVariable(localVar); + sprite.addVariable(counter1); + sprite.addVariable(counter2); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(true); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(0); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(counter1.get(), v); + + m_builder->beginLoopCondition(); + CompilerValue *v1 = m_builder->addVariableValue(counter1.get()); + CompilerValue *v2 = m_builder->addConstValue(5); + v = m_builder->createCmpLT(v1, v2); + m_builder->beginWhileLoop(v); + { + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(counter2.get(), v); + + m_builder->beginLoopCondition(); + v1 = m_builder->addVariableValue(counter2.get()); + v2 = m_builder->addConstValue(3); + v = m_builder->createCmpEQ(v1, v2); + m_builder->beginRepeatUntilLoop(v); + { + v = m_builder->addConstValue(true); + m_builder->createVariableWrite(globalVar.get(), v); + + v1 = m_builder->addVariableValue(counter2.get()); + v2 = m_builder->addConstValue(1); + v = m_builder->createAdd(v1, v2); + m_builder->createVariableWrite(counter2.get(), v); + } + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(false); + m_builder->beginWhileLoop(v); + { + v = m_builder->addConstValue(-8.2); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + + v1 = m_builder->addVariableValue(counter1.get()); + v2 = m_builder->addConstValue(1); + v = m_builder->createAdd(v1, v2); + m_builder->createVariableWrite(counter1.get(), v); + } + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(v); + { + v = m_builder->addConstValue(-8.2); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + std::string expected = + "hello world\n" + "12.5\n" + "true\n" + "true\n" + "true\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, VariableLoopTypeAnalysis1) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\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, VariableLoopTypeAnalysis2) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "12.5\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, VariableLoopTypeAnalysis3) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with unknown type is assigned (the variable has unknown type because a string is assigned later) + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\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, VariableLoopTypeAnalysis4) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a variable is assigned which has a number assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "12.5\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, VariableLoopTypeAnalysis5) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string variable is assigned later which has a number assigned as well + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "12.5\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, VariableLoopTypeAnalysis6) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a variable with known type is assigned later (even though the variable has a string assigned later, there's a number assigned before the read operation) + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "10\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, VariableLoopTypeAnalysis7) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with a known, but different type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\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, VariableLoopTypeAnalysis8) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable with a known, but different type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\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, VariableLoopTypeAnalysis9) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", "", "123"); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", ""); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + + // Type is unknown here because a variable of unknown type is assigned before the read operation + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5\n" + "5\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, VariableLoopTypeAnalysis10) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable of unknown type is assigned later + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "12.5\n" + "12.5\n" + "0\n" + "12.5\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, VariableLoopTypeAnalysis11) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because the variable is assigned to itself later + // This case is not checked because the code isn't considered valid + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "123\n" + "123\n" + "123\n" + "123\n" + "123\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, VariableLoopTypeAnalysis12) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalVar = std::make_shared("", ""); + stage.addVariable(globalVar); + + auto localVar = std::make_shared("", "", "123"); + sprite.addVariable(localVar); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(globalVar.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because another variable is assigned later, but it has this variable assigned + // This case is not checked because the code isn't considered valid + v = m_builder->addVariableValue(localVar.get()); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addVariableValue(globalVar.get()); + m_builder->createVariableWrite(localVar.get(), v); + + v = m_builder->addVariableValue(localVar.get()); + m_builder->createVariableWrite(globalVar.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "0\n" + "0\n" + "0\n" + "0\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, LoopLists) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList1 = std::make_shared("", ""); + stage.addList(globalList1); + + auto globalList2 = std::make_shared("", ""); + stage.addList(globalList2); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + auto counter1 = std::make_shared("", ""); + auto counter2 = std::make_shared("", ""); + sprite.addVariable(counter1); + sprite.addVariable(counter2); + + createBuilder(&sprite, true); + + auto resetLists = [this, globalList1, globalList2, localList]() { + m_builder->createListClear(globalList1.get()); + m_builder->createListClear(globalList2.get()); + m_builder->createListClear(localList.get()); + + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(1)); + m_builder->createListAppend(globalList1.get(), m_builder->addConstValue(2)); + + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("hello")); + m_builder->createListAppend(globalList2.get(), m_builder->addConstValue("world")); + + m_builder->createListAppend(localList.get(), m_builder->addConstValue(false)); + m_builder->createListAppend(localList.get(), m_builder->addConstValue(true)); + }; + + auto checkLists = [this, globalList1, globalList2, localList]() { + CompilerValue *v = m_builder->addListItem(globalList1.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(globalList2.get(), m_builder->addConstValue(1)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + }; + + // repeat (2) + resetLists(); + + CompilerValue *v, *v1, *v2; + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endLoop(); + + checkLists(); + + // repeat(0) + resetLists(); + + v = m_builder->addConstValue(0); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endLoop(); + + checkLists(); + + // while (counter1 < 5) { ... until (counter2 == 3) {... counter2++ }; while (false) { ... }; counter1++ } + resetLists(); + + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(counter1.get(), v); + + m_builder->beginLoopCondition(); + v1 = m_builder->addVariableValue(counter1.get()); + v2 = m_builder->addConstValue(5); + v = m_builder->createCmpLT(v1, v2); + m_builder->beginWhileLoop(v); + { + v = m_builder->addConstValue(0); + m_builder->createVariableWrite(counter2.get(), v); + + m_builder->beginLoopCondition(); + v1 = m_builder->addVariableValue(counter2.get()); + v2 = m_builder->addConstValue(3); + v = m_builder->createCmpEQ(v1, v2); + m_builder->beginRepeatUntilLoop(v); + { + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + + v1 = m_builder->addVariableValue(counter2.get()); + v2 = m_builder->addConstValue(1); + v = m_builder->createAdd(v1, v2); + m_builder->createVariableWrite(counter2.get(), v); + } + m_builder->endLoop(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(false); + m_builder->beginWhileLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + } + m_builder->endLoop(); + + v1 = m_builder->addVariableValue(counter1.get()); + v2 = m_builder->addConstValue(1); + v = m_builder->createAdd(v1, v2); + m_builder->createVariableWrite(counter1.get(), v); + } + m_builder->endLoop(); + + checkLists(); + + // until (true) + resetLists(); + + m_builder->beginLoopCondition(); + v = m_builder->addConstValue(true); + m_builder->beginRepeatUntilLoop(v); + { + v = m_builder->addConstValue("hello world"); + m_builder->createListAppend(globalList1.get(), v); + + v1 = m_builder->addConstValue(0); + v2 = m_builder->addConstValue(8.5); + m_builder->createListInsert(globalList2.get(), v1, v2); + + v1 = m_builder->addConstValue(1); + v2 = m_builder->addConstValue(-4.25); + m_builder->createListReplace(localList.get(), v1, v2); + } + m_builder->endLoop(); + + checkLists(); + + std::string expected = + "1\n" + "8.5\n" + "false\n" + "1\n" + "world\n" + "false\n" + "1\n" + "8.5\n" + "false\n" + "1\n" + "world\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, ListLoopTypeAnalysis1) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue("test"); + m_builder->createListAppend(localList.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\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, ListLoopTypeAnalysis2) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "12.5\n" + "-1\n" + "0\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, ListLoopTypeAnalysis3) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is inserted (the item has unknown type because a string is assigned later) + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListInsert(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue("test"); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "2\n" + "1\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, ListLoopTypeAnalysis4) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(0); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is known here because a number list item is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "12.5\n" + "0\n" + "1\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, ListLoopTypeAnalysis5) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a string list item is assigned later which becomes a number later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\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, ListLoopTypeAnalysis6) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(globalList.get()); + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(-156.07); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is added later + v = m_builder->createSub(m_builder->addListSize(localList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->createSub(m_builder->addListSize(globalList.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(globalList.get(), v); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\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, ListLoopTypeAnalysis7) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with unknown type is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(10); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\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, ListLoopTypeAnalysis8) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a list item with a unknown type is assigned later + v = m_builder->addConstValue(0); + v = m_builder->addListItem(localList.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\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, ListLoopTypeAnalysis9) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + + createBuilder(&sprite, true); + + m_builder->createListClear(localList.get()); + + CompilerValue *v = m_builder->addConstValue(5.25); + m_builder->createListAppend(localList.get(), v); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(5); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + + // Type is unknown here because a list item with unknown type is assigned before the read operation + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(5)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(5)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue(10); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue("test"); + m_builder->createListInsert(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } m_builder->endLoop(); - // Nested - m_builder->addFunctionCall("test_reset_counter", Compiler::StaticType::Void, {}); - m_builder->beginLoopCondition(); - m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}); - m_builder->addConstValue(3); - m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }); - m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->beginRepeatUntilLoop(); + std::string expected = + "5\n" + "0\n" + "1\n" + "5\n" + "0\n" + "1\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, ListLoopTypeAnalysis10) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); { - m_builder->beginLoopCondition(); - m_builder->addFunctionCall("test_get_counter", Compiler::StaticType::Number, {}); - m_builder->addConstValue(3); - m_builder->addFunctionCall("test_lower_than", Compiler::StaticType::Bool, { Compiler::StaticType::Number, Compiler::StaticType::Number }); - m_builder->addFunctionCall("test_not", Compiler::StaticType::Bool, { Compiler::StaticType::Bool }); - m_builder->beginRepeatUntilLoop(); + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); { - m_builder->addConstValue(1); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); - m_builder->addFunctionCall("test_increment_counter", Compiler::StaticType::Void, {}); + // Type is unknown here because a list item with unknown type is assigned later + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); } m_builder->endLoop(); + } + m_builder->endLoop(); - m_builder->addConstValue(2); - m_builder->addFunctionCall("test_function_1_arg", Compiler::StaticType::Void, { Compiler::StaticType::String }); + std::string expected = + "123\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n" + "12.5\n" + "-1\n" + "0\n"; - m_builder->beginLoopCondition(); - m_builder->addConstValue(true); - m_builder->beginRepeatUntilLoop(); + 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, ListLoopTypeAnalysis11) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + localList->append("10"); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); { - m_builder->addFunctionCall("test_unreachable", Compiler::StaticType::Void, {}); + // Type is unknown here because an item from the same list is assigned later, but the list has unknown type + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(1)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addConstValue(12.5); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); } m_builder->endLoop(); } m_builder->endLoop(); + std::string expected = + "123\n" + "0\n" + "1\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n" + "10\n" + "-1\n" + "0\n"; + auto code = m_builder->finalize(); - auto ctx = code->createExecutionContext(&m_target); + 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); +} - 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"; +TEST_F(LLVMCodeBuilderTest, ListLoopTypeAnalysis12) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); - EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + auto globalList = std::make_shared("", ""); + stage.addList(globalList); + + auto localList = std::make_shared("", ""); + sprite.addList(localList); + localList->append(123); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + { + v = m_builder->addConstValue("test"); + m_builder->createListAppend(globalList.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because an item from another list is assigned later, but it has an item from this list assigned + // This case is not checked because the code isn't considered valid + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(localList.get(), m_builder->addConstValue(123)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addListItem(globalList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(localList.get(), m_builder->addConstValue(0), v); + + v = m_builder->addListItem(localList.get(), m_builder->addConstValue(0)); + m_builder->createListReplace(globalList.get(), m_builder->addConstValue(0), v); + } + m_builder->endLoop(); + } + m_builder->endLoop(); + + std::string expected = + "123\n" + "0\n" + "1\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\n" + "0\n" + "-1\n" + "0\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); +} - // Yield - createBuilder(false); +TEST_F(LLVMCodeBuilderTest, VarAndListLoopTypeAnalysis) +{ + Stage stage; + Sprite sprite; + sprite.setEngine(&m_engine); + EXPECT_CALL(m_engine, stage()).WillRepeatedly(Return(&stage)); + + auto var = std::make_shared("", ""); + sprite.addVariable(var); + + auto list = std::make_shared("", ""); + sprite.addList(list); + + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(-2); + m_builder->createVariableWrite(var.get(), v); + + m_builder->createListClear(list.get()); + + v = m_builder->addConstValue(5.25); + m_builder->createListAppend(list.get(), v); + + v = m_builder->addConstValue(3); + m_builder->beginRepeatLoop(v); + { + // Type is unknown here because a variable value with unknown type is added later + v = m_builder->createSub(m_builder->addListSize(list.get()), m_builder->addConstValue(1)); + v = m_builder->addListItem(list.get(), v); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListItemIndex(list.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addListContains(list.get(), m_builder->addConstValue(5.25)); + m_builder->addFunctionCall("test_print_bool", Compiler::StaticType::Void, { Compiler::StaticType::Bool }, { v }); + + v = m_builder->addVariableValue(var.get()); + m_builder->createListAppend(list.get(), v); + + v = m_builder->addConstValue("test"); + m_builder->createVariableWrite(var.get(), v); + } + m_builder->endLoop(); + + std::string expected = + "5.25\n" + "0\n" + "1\n" + "-2\n" + "0\n" + "1\n" + "0\n" + "0\n" + "1\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, StopNoWarp) +{ + Sprite sprite; + createBuilder(&sprite, false); m_builder->beginLoopCondition(); - m_builder->addConstValue(false); - m_builder->beginRepeatUntilLoop(); - m_builder->addFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}); + CompilerValue *v = m_builder->addConstValue(true); + m_builder->beginWhileLoop(v); + m_builder->createStop(); m_builder->endLoop(); - code = m_builder->finalize(); - ctx = code->createExecutionContext(&m_target); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); - static const std::string expected1 = "no_args\n"; + std::string expected = ""; - EXPECT_CALL(m_target, isStage).WillRepeatedly(Return(false)); + 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); +} - for (int i = 0; i < 10; i++) { - testing::internal::CaptureStdout(); - code->run(ctx.get()); - ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); - ASSERT_FALSE(code->isFinished(ctx.get())); - } +TEST_F(LLVMCodeBuilderTest, StopWarp) +{ + Sprite sprite; + createBuilder(&sprite, true); + + CompilerValue *v = m_builder->addConstValue(true); + m_builder->beginIfStatement(v); + m_builder->createStop(); + m_builder->endIf(); + + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + + std::string expected = ""; + + 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, StopAndReturn) +{ + Sprite sprite; + createBuilder(&sprite, true); + + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->createStop(); + + std::string expected = "no_args\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, MultipleScripts) +{ + Sprite sprite; + + // Script 1 + createBuilder(&sprite, nullptr); + + CompilerValue *v = m_builder->addConstValue("script1"); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto code1 = m_builder->finalize(); + + // Script 2 + createBuilder(&sprite, nullptr); + + v = m_builder->addConstValue("script2"); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + auto code2 = m_builder->finalize(); + + Script script1(&sprite, nullptr, nullptr); + script1.setCode(code1); + Thread thread1(&sprite, nullptr, &script1); + auto ctx = code1->createExecutionContext(&thread1); + + testing::internal::CaptureStdout(); + code1->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "script1\n"); + + Script script2(&sprite, nullptr, nullptr); + script2.setCode(code2); + Thread thread2(&sprite, nullptr, &script2); + ctx = code2->createExecutionContext(&thread2); + + testing::internal::CaptureStdout(); + code2->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), "script2\n"); +} + +TEST_F(LLVMCodeBuilderTest, Procedures) +{ + Sprite sprite; + auto var = std::make_shared("", ""); + auto list = std::make_shared("", ""); + sprite.addVariable(var); + sprite.addList(list); + + // Procedure 1 + BlockPrototype prototype1; + prototype1.setProcCode("procedure 1 %s %s %b"); + prototype1.setArgumentNames({ "any type 1", "any type 2", "bool" }); + prototype1.setArgumentIds({ "a", "b", "c" }); + prototype1.setWarp(false); + createBuilder(&sprite, &prototype1); + + CompilerValue *v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + m_builder->addTargetFunctionCall("test_function_no_args", Compiler::StaticType::Void, {}, {}); + m_builder->endLoop(); + + m_builder->createVariableWrite(var.get(), m_builder->addProcedureArgument("any type 1")); + m_builder->createListClear(list.get()); + m_builder->createListAppend(list.get(), m_builder->addProcedureArgument("any type 2")); + + v = m_builder->addVariableValue(var.get()); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addListItem(list.get(), m_builder->addConstValue(0)); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + v = m_builder->addProcedureArgument("bool"); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + m_builder->addFunctionCall("test_print_number", Compiler::StaticType::Void, { Compiler::StaticType::Number }, { v }); + + v = m_builder->addProcedureArgument("invalid"); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + m_builder->finalize(); + + // Procedure 2 + BlockPrototype prototype2; + prototype2.setProcCode("procedure 2"); + prototype2.setWarp(true); + createBuilder(&sprite, &prototype2); + + v = m_builder->addConstValue(2); + m_builder->beginRepeatLoop(v); + m_builder->createProcedureCall(&prototype1, { m_builder->addConstValue(-652.3), m_builder->addConstValue(false), m_builder->addConstValue(true) }); + m_builder->endLoop(); + + m_builder->finalize(); + + // Script + createBuilder(&sprite, false); + m_builder->createProcedureCall(&prototype1, { m_builder->addConstValue("test"), m_builder->addConstValue(true), m_builder->addConstValue(false) }); + m_builder->createProcedureCall(&prototype2, {}); + + v = m_builder->addProcedureArgument("test"); + m_builder->addFunctionCall("test_print_string", Compiler::StaticType::Void, { Compiler::StaticType::String }, { v }); + + std::string expected1 = "no_args\n"; + + std::string expected2 = + "test\n" + "true\n" + "false\n" + "0\n" + "0\n"; + + std::string expected3 = + "no_args\n" + "no_args\n" + "-652.3\n" + "false\n" + "true\n" + "1\n" + "0\n" + "no_args\n" + "no_args\n" + "-652.3\n" + "false\n" + "true\n" + "1\n" + "0\n" + "0\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(), expected1); + ASSERT_FALSE(code->isFinished(ctx.get())); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected1); + ASSERT_FALSE(code->isFinished(ctx.get())); + + testing::internal::CaptureStdout(); + code->run(ctx.get()); + ASSERT_EQ(testing::internal::GetCapturedStdout(), expected2 + expected3); + ASSERT_TRUE(code->isFinished(ctx.get())); } diff --git a/test/dev/llvm/llvmexecutablecode_test.cpp b/test/dev/llvm/llvmexecutablecode_test.cpp index fea3f778..08427a32 100644 --- a/test/dev/llvm/llvmexecutablecode_test.cpp +++ b/test/dev/llvm/llvmexecutablecode_test.cpp @@ -1,10 +1,16 @@ #include #include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include -#include +#include #include "testmock.h" #include "testfunctions.h" @@ -16,25 +22,26 @@ class LLVMExecutableCodeTest : public testing::Test public: void SetUp() override { - m_module = std::make_unique("test", m_ctx); - m_builder = std::make_unique>(m_ctx); - test_function(nullptr, nullptr); // force dependency + m_target.setName("test"); + m_ctx = std::make_unique(&m_engine, &m_target); + m_module = m_ctx->module(); + m_llvmCtx = m_ctx->llvmCtx(); + m_builder = std::make_unique>(*m_llvmCtx); + test_function(nullptr, nullptr, nullptr, nullptr, nullptr); // force dependency - llvm::InitializeNativeTarget(); - llvm::InitializeNativeTargetAsmPrinter(); - llvm::InitializeNativeTargetAsmParser(); + m_script = std::make_unique