diff --git a/.clang-format b/.clang-format index b5be3ec7..350b5f89 100644 --- a/.clang-format +++ b/.clang-format @@ -15,7 +15,7 @@ AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Always AllowShortCaseLabelsOnASingleLine: true AllowShortEnumsOnASingleLine: false -AllowShortFunctionsOnASingleLine: Inline +AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: Inline AllowShortLoopsOnASingleLine: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92af0cac..a7c2ee7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -74,7 +74,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}}/build - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE + run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DINKCPP_PY=ON # Build using CMake and OS toolkit - name: Build @@ -125,6 +125,17 @@ jobs: name: ${{ matrix.artifact }}-unreal path: build/comp_unreal/ + - name: Install PythonLib + working-directory: ${{github.workspace}}/build + shell: bash + run: cmake --install . --config $BUILD_TYPE --prefix comp_py --component unreal + + - name: Upload Binary Artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.artifact }}-py + path: build/comp_py/ + # Make sure Inkproof has everything it needs to run our executable - name: Setup Ink Proof if: ${{ matrix.proof }} @@ -170,6 +181,33 @@ jobs: name: ${{ matrix.artifact }}-www path: proofing/ink-proof/out + + build-python: + name: Build Python package + needs: compilation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + - name: install build + run: >- + python3 -m + pip install + build + --user + - name: Build python release + run: python3 -m build + - name: Upload Python files + uses: actions/upload-artifact@v3 + with: + name: python-package-distribution + path: dist/ + clang-format: name: "Check Formatting" runs-on: ubuntu-latest @@ -178,13 +216,13 @@ jobs: - uses: actions/checkout@v3 - name: Fetch master branch run: | - git fetch origin master --depth 1 + git fetch origin master - name: Check clang-format run: | - diff=$(git clang-format-14 --style file -q --diff origin/master) + diff=$(git clang-format-14 --extensions c,cpp,h,hpp --style file -q --diff origin/master) echo $diff if [ "$diff" != "" ]; then - echo run git clang-format --style file master + echo run git clang-format --extensions c,cpp,h,hpp --style file master echo or upstream/master depending on your setup clang fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 924ebe8a..4fdcc7cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,20 +12,27 @@ jobs: release: name: Release pushed tag runs-on: ubuntu-22.04 + enviroment: + name: pypi + url: https://pypi.org/p/inkcpp-py + permissions: + id-token: write steps: - uses: actions/checkout@v3 - name: Download artifacts uses: marcofaggian/action-download-multiple-artifacts@v3.0.8 with: - names: linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal - paths: linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal + names: linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal python-package-distribution + paths: linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal dist/ workflow: build.yml branch: master - name: Zip run: | - for f in linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal; do zip -r $f $f; done + for f in linux-cl linux-lib linux-unreal macos-cl macos-lib macos-unreal win64-cl win64-lib win64-unreal win6; do zip -r $f $f; done - name: List run: tree + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -35,4 +42,5 @@ jobs: --repo="$GITHUB_REPOSITORY" \ --title="${GITHUB_REPOSITORY#*/} ${tag#v}" \ --generate-notes \ - "$tag" "linux-cl.zip" "linux-lib.zip" "linux-unreal.zip" "macos-cl.zip" "macos-lib.zip" "macos-unreal.zip" "win64-cl.zip" "win64-lib.zip" "win64-unreal.zip" + "$tag" "linux-cl.zip" "linux-lib.zip" "linux-unreal.zip" "macos-cl.zip" "macos-lib.zip" "macos-unreal.zip" "win64-cl.zip" "win64-lib.zip" "win64-unreal.zip" + diff --git a/.gitignore b/.gitignore index b0c621a7..4f4b9f8b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # Ink files inkcpp_cl/*.ink *.json +!inkcpp_py/sources.json # Visual Studio code *.code-workspace @@ -16,4 +17,4 @@ Documentation/* Build/* build/* bin/ -Bin/ \ No newline at end of file +Bin/ diff --git a/.gitmodules b/.gitmodules index fa05f404..27948af3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "proofing/ink-proof"] path = proofing/ink-proof url = https://github.com/chromy/ink-proof.git + shallow = true +[submodule "inkcpp_py/pybind11"] + path = inkcpp_py/pybind11 + url = https://github.com/pybind/pybind11.git + branch = stable + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index 6740f81b..47073309 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,18 +4,29 @@ cmake_minimum_required(VERSION 3.16) enable_testing() # Project setup -project(inkcpp VERSION 0.1) +project(inkcpp VERSION 0.1.1) SET(CMAKE_CXX_STANDARD 20) SET(CMAKE_CXX_STANDARD_REQUIRED ON) SET(CMAKE_INSTALL_LIBRARY_DIR lib) SET(CMAKE_INSTALL_INCLUDE_DIR include) + # Add subdirectories +set(INKCPP_PY OFF CACHE BOOL "Build python bindings") +set(WHEEL_BUILD OFF CACHE BOOL "Set for build wheel python lib (do not forgett INKCPP_PY") + +if (INKCPP_PY) + add_compile_options(-fPIC) + add_subdirectory(inkcpp_py) +endif(INKCPP_PY) add_subdirectory(shared) add_subdirectory(inkcpp) add_subdirectory(inkcpp_compiler) +if (NOT WHEEL_BUILD) add_subdirectory(inkcpp_cl) add_subdirectory(inkcpp_test) add_subdirectory(unreal) +endif(NOT WHEEL_BUILD) + install(TARGETS inkcpp inkcpp_compiler inkcpp_shared diff --git a/README.md b/README.md index 2304c1b2..3688c39f 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,13 @@ Without the `-p` flag, it'll just compile the JSON/Ink file into InkCPP's binary All features of ink 1.1 are supported, and checked with [ink-proof](https://github.com/chromy/ink-proof). +In addition a UE Plugin inclusive BluePrints are provided and python bindings based on [pybind11](https://github.com/pybind/pybind11). + KeyFeatures: snapshots, observers, binding ink functions, support ink [function fallback](https://github.com/inkle/ink/blob/master/Documentation/RunningYourInk.md#fallbacks-for-external-functions) ## Unreal Plugin -The current version of the UE plugin can be downloaded from the [release page](https://github.com/brwarner/inkcpp/releases/latest) with te corresponding name of the OS (e.g. win64-unreal). +The current version of the UE plugin can be downloaded from the [release page](https://github.com/JBenda/inkcpp/releases/latest) with te corresponding name of the OS (e.g. win64-unreal). Place the content of this file at your plugin folder of your UE project and at the next start up it will be intigrated. A example project can be found [here](https://cloud.julian-benda.de/index.php/s/cRMBGBWbHPCcdwb). @@ -125,7 +127,7 @@ To install the different components use `cmake --install . --component _py` eg `linux_py`. ## Dependencies The compiler depends on Nlohmann's JSON library and the C++ STL. diff --git a/inkcpp/functional.cpp b/inkcpp/functional.cpp index b4849651..5bdde70d 100644 --- a/inkcpp/functional.cpp +++ b/inkcpp/functional.cpp @@ -5,73 +5,76 @@ #include "string_table.h" #ifdef INK_ENABLE_UNREAL -#include "InkVar.h" +# include "InkVar.h" #endif namespace ink::runtime::internal { - template<> - int32_t function_base::pop(basic_eval_stack* stack, list_table& lists) - { - value val = stack->pop(); - inkAssert(val.type() == value_type::int32, "Type missmatch!"); - return val.get(); - } +template<> +ink::runtime::value + function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + return val.to_interface_value(lists); +} - template<> - const char* function_base::pop(basic_eval_stack* stack, list_table& lists) - { - value val = stack->pop(); - inkAssert(val.type() == value_type::string, "Type missmatch!"); - return val.get().str; - } +template<> +int32_t function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + inkAssert(val.type() == value_type::int32, "Type missmatch!"); + return val.get(); +} - template<> - void function_base::push(basic_eval_stack* stack, const int32_t& v) - { - stack->push(value{}.set(v)); - } +template<> +const char* function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + value val = stack->pop(); + inkAssert(val.type() == value_type::string, "Type missmatch!"); + return val.get().str; +} - void function_base::push_void(basic_eval_stack* stack) - { - stack->push(values::null); - } +template<> +void function_base::push(basic_eval_stack* stack, const int32_t& v) +{ + stack->push(value{}.set(v)); +} +void function_base::push_void(basic_eval_stack* stack) { stack->push(values::null); } - void function_base::push_string(basic_eval_stack* stack, const char* dynamic_string) - { - stack->push(value{}.set(dynamic_string, true)); - } +void function_base::push_string(basic_eval_stack* stack, const char* dynamic_string) +{ + stack->push(value{}.set(dynamic_string, true)); +} - char* function_base::allocate(string_table& strings, size_t len) - { - return strings.create(len); - } +char* function_base::allocate(string_table& strings, size_t len) { return strings.create(len); } - // Generate template implementations for all significant types +// Generate template implementations for all significant types #ifdef INK_ENABLE_STL - template<> - std::string function_base::pop(basic_eval_stack* stack, list_table& lists) { - return std::string(pop(stack, lists)); - } +template<> +std::string function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + return std::string(pop(stack, lists)); +} #endif #ifdef INK_ENABLE_UNREAL - template<> - FInkVar function_base::pop(basic_eval_stack* stack, list_table& lists) - { - return FInkVar(stack->pop().to_interface_value(lists)); - } - - template<> - void function_base::push(basic_eval_stack* stack, const ink::runtime::value& value) - { - internal::value val{}; - if(val.set(value)) { - stack->push(val); - } else { - inkFail("unable to set variable?"); - } - } +template<> +FInkVar function_base::pop(basic_eval_stack* stack, list_table& lists) +{ + return FInkVar(stack->pop().to_interface_value(lists)); +} #endif +template<> +void function_base::push( + basic_eval_stack* stack, const ink::runtime::value& value +) +{ + internal::value val{}; + if (val.set(value)) { + stack->push(val); + } else { + inkFail("unable to set variable?"); + } } +} // namespace ink::runtime::internal diff --git a/inkcpp/include/functional.h b/inkcpp/include/functional.h index 94b49c0a..0cbc145e 100644 --- a/inkcpp/include/functional.h +++ b/inkcpp/include/functional.h @@ -1,252 +1,310 @@ #pragma once +#include "config.h" #include "list.h" #include "traits.h" #include "system.h" #include "types.h" -#ifdef INK_ENABLE_UNREAL -#include "../InkVar.h" +#ifdef INK_ENABLE_UNREAL +# include "../InkVar.h" #endif namespace ink::runtime::internal { - class basic_eval_stack; - class string_table; - class list_table; +class basic_eval_stack; +class string_table; +class list_table; + +class callback_base +{ +public: + virtual void call(ink::runtime::value, ink::optional) = 0; +}; - class callback_base { - public: - virtual void call(ink::runtime::value, ink::optional) = 0; - }; +template +class callback final : public callback_base +{ + using traits = function_traits; + static_assert(traits::arity < 3); - template - class callback final : public callback_base + template + void call_functor(ink::runtime::value new_val, ink::optional old_val) { - using traits = function_traits; - static_assert(traits::arity < 3); - - template - void call_functor(ink::runtime::value new_val, ink::optional old_val) { - if constexpr(traits::arity == 2) { - if (old_val.has_value()) { - functor(new_val.get(), typename traits::template argument<1>::type{old_val.value().get()}); - } else { - functor(new_val.get(), ink::nullopt); - } + if constexpr (traits::arity == 2) { + if (old_val.has_value()) { + functor( + new_val.get(), + typename traits::template argument<1>::type{old_val.value().get()} + ); } else { - functor(new_val.get()); + functor(new_val.get(), ink::nullopt); } + } else { + functor(new_val.get()); } + } - public: - callback( const callback& ) = delete; - callback( callback&& ) = delete; - callback& operator=( const callback& ) = delete; - callback& operator=( callback&& ) = delete; - callback( F functor ) - : functor( functor ) {} - - virtual void call(ink::runtime::value new_val, - ink::optional old_val) { - using value = ink::runtime::value; - auto check_type = [&new_val, &old_val](value::Type type){ - inkAssert(new_val.type == type, - "Missmatch type for variable observer: expected %i, got %i", - static_cast(type), - static_cast(new_val.type)); +public: + callback(const callback&) = delete; + callback(callback&&) = delete; + callback& operator=(const callback&) = delete; + callback& operator=(callback&&) = delete; + + callback(F functor) + : functor(functor) + { + } + + virtual void call(ink::runtime::value new_val, ink::optional old_val) + { + using value = ink::runtime::value; + auto check_type = [&new_val, &old_val](value::Type type) { + inkAssert( + new_val.type == type, "Missmatch type for variable observer: expected %i, got %i", + static_cast(type), static_cast(new_val.type) + ); + if constexpr (traits::arity == 2) { + // inkAssert(!old_val.has_value() || old_val.value().type == type, + // "Missmatch type for variable observers old value: expected optional<%i> got + // optional<%i>", static_cast(type), static_cast(old_val.value().type)); + } + }; + if constexpr (traits::arity > 0) { + using arg_t = typename remove_cvref::type>::type; + if constexpr (is_same::value) { if constexpr (traits::arity == 2) { - // inkAssert(!old_val.has_value() || old_val.value().type == type, - // "Missmatch type for variable observers old value: expected optional<%i> got optional<%i>", - // static_cast(type), - // static_cast(old_val.value().type)); - } - }; - if constexpr (traits::arity > 0) { - using arg_t = typename remove_cvref::type>::type; - if constexpr (is_same::value) { - if constexpr (traits::arity == 2) { - functor(new_val, old_val); - } else { - functor(new_val); - } - } else if constexpr (is_same::value) { - check_type(value::Type::Bool); - call_functor(new_val, old_val); - } else if constexpr (is_same::value) { - check_type(value::Type::Uint32); - call_functor(new_val, old_val); - } else if constexpr (is_same::value) { - check_type(value::Type::Int32); - call_functor(new_val, old_val); - } else if constexpr (is_same::value) { - check_type(value::Type::String); - call_functor(new_val, old_val); - } else if constexpr (is_same::value) { - check_type(value::Type::Float); - call_functor(new_val, old_val); - } else if constexpr (is_same::value){ - check_type(value::Type::List); - call_functor(new_val, old_val); + functor(new_val, old_val); } else { - static_assert(always_false::value, "Unsupported value for variable observer callback!"); + functor(new_val); } + } else if constexpr (is_same::value) { + check_type(value::Type::Bool); + call_functor(new_val, old_val); + } else if constexpr (is_same::value) { + check_type(value::Type::Uint32); + call_functor(new_val, old_val); + } else if constexpr (is_same::value) { + check_type(value::Type::Int32); + call_functor(new_val, old_val); + } else if constexpr (is_same::value) { + check_type(value::Type::String); + call_functor(new_val, old_val); + } else if constexpr (is_same::value) { + check_type(value::Type::Float); + call_functor(new_val, old_val); + } else if constexpr (is_same::value) { + check_type(value::Type::List); + call_functor(new_val, old_val); } else { - functor(); + static_assert( + always_false::value, "Unsupported value for variable observer callback!" + ); } + } else { + functor(); } - private: - F functor; - }; + } - // base function container with virtual callback methods - class function_base - { - public: - virtual ~function_base() { } +private: + F functor; +}; - // calls the underlying function object taking parameters from a stack +// base function container with virtual callback methods +class function_base +{ +public: + virtual ~function_base() {} + + // calls the underlying function object taking parameters from a stack #ifdef INK_ENABLE_UNREAL - virtual void call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists) = 0; + virtual void + call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists) + = 0; #else - virtual void call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists) = 0; + virtual void + call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists) + = 0; #endif - protected: - // used to hide basic_eval_stack and value definitions - template - static T pop(basic_eval_stack* stack, list_table& lists); +protected: + // used to hide basic_eval_stack and value definitions + template + static T pop(basic_eval_stack* stack, list_table& lists); - // used to hide basic_eval_stack and value definitions - template - static void push(basic_eval_stack* stack, const T& value); + // used to hide basic_eval_stack and value definitions + template + static void push(basic_eval_stack* stack, const T& value); - static void push_void(basic_eval_stack* stack); + static void push_void(basic_eval_stack* stack); - // string special push - static void push_string(basic_eval_stack* stack, const char* dynamic_string); + // string special push + static void push_string(basic_eval_stack* stack, const char* dynamic_string); - // used to hide string_table definitions - static char* allocate(string_table& strings, ink::size_t len); - }; + // used to hide string_table definitions + static char* allocate(string_table& strings, ink::size_t len); +}; - // Stores a Callable function object and forwards calls to it - template - class function : public function_base +// Stores a Callable function object and forwards calls to it +template +class function : public function_base +{ +public: + function(F functor) + : functor(functor) { - public: - function(F functor) : functor(functor) { } + } - // calls the underlying function using arguments on the stack - virtual void call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists) override - { - call(stack, length, strings, lists, GenSeq()); - } + // calls the underlying function using arguments on the stack + virtual void call( + basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists + ) override + { + call(stack, length, strings, lists, GenSeq()); + } - private: - // Callable functor object - F functor; +private: + // Callable functor object + F functor; - // function traits - using traits = function_traits; + // function traits + using traits = function_traits; - // argument types - template - using arg_type = typename function_traits::template argument::type; + // argument types + template + using arg_type = typename function_traits::template argument::type; - // pops an argument from the stack using the function-type - template - arg_type pop_arg(basic_eval_stack* stack, list_table& lists) - { - // todo - type assert? + // pops an argument from the stack using the function-type + template + arg_type pop_arg(basic_eval_stack* stack, list_table& lists) + { + // todo - type assert? + return pop>(stack, lists); + } - return pop>(stack, lists); + static constexpr bool is_array_call() + { + if constexpr (traits::arity == 2) { + return is_same< + const ink::runtime::value*, typename traits::template argument<1>::type>::value; } + return false; + } - template - void call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists, seq) - { - // Make sure the argument counts match - inkAssert(sizeof...(Is) == length, "Attempting to call functor with too few/many arguments"); - static_assert(sizeof...(Is) == traits::arity); - - // void functions - if constexpr (is_same::value) - { - // Just evaluevaluatelate + template + void + call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists, seq) + { + inkAssert( + is_array_call() || sizeof...(Is) == length, + "Attempting to call functor with too few/many arguments" + ); + static_assert(sizeof...(Is) == traits::arity); + if_t vals; + if constexpr (is_array_call()) { + inkAssert( + length <= config::maxArrayCallArity, + "AIttampt to call array call with more arguments then supportet, please change in " + "config" + ); + for (size_t i = 0; i < length; ++i) { + vals[i] = pop(stack, lists); + } + } + // void functions + if constexpr (is_same::value) { + // Just evaluevaluatelate + if constexpr (is_array_call()) { + functor(length, vals); + } else { functor(pop_arg(stack, lists)...); - - // Ink expects us to push something - // TODO -- Should be a special "void" value - push_void(stack); } - else if constexpr (is_string::value) - { + + // Ink expects us to push something + push_void(stack); + } else { + typename traits::return_type res; + if constexpr (is_array_call()) { + res = functor(length, vals); + } else { + res = functor(pop_arg(stack, lists)...); + } + if constexpr (is_string::value) { // SPECIAL: The result of the functor is a string type - // in order to store it in the inkcpp interpreter we + // in order to store it in the inkcpp interpreter we // need to store it in our allocated string table - auto string_result = functor(pop_arg(stack, lists)...); - // Get string length - size_t len = string_handler::length(string_result); + size_t len = string_handler::length(res); // Get source and allocate buffer char* buffer = allocate(strings, len + 1); - string_handler::src_copy(string_result, buffer); + string_handler::src_copy(res, buffer); // push string result push_string(stack, buffer); - } - else - { + } else if constexpr (is_same>::value) { + if (res.type() == ink::runtime::value::Type::String) { + auto src = res.template get(); + size_t len = string_handler::length(src); + char* buffer = allocate(strings, len + 1); + string_handler::src_copy(src, buffer); + push_string(stack, buffer); + } else { + push(stack, res); + } + } else { // Evaluate and push the result onto the stack - push(stack, functor(pop_arg(stack, lists)...)); + push(stack, res); } } - }; + } +}; #ifdef INK_ENABLE_UNREAL - template - class function_array_delegate : public function_base +template +class function_array_delegate : public function_base +{ +public: + function_array_delegate(const D& del) + : invocableDelegate(del) { - public: - function_array_delegate(const D& del) : invocableDelegate(del) { } - - // calls the underlying delegate using arguments on the stack - virtual void call(basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists) override - { - constexpr bool RET_VOID = - is_same::return_type, - void>::value; - // Create variable array - TArray variables; - for (size_t i = 0; i < length; i++) - { - variables.Add(pop(stack, lists)); - } - if constexpr (RET_VOID) - { - invocableDelegate.Execute(variables); - push(stack, 0); - } else { - - auto ret = invocableDelegate.Execute(variables); - ink::runtime::value result = ret.to_value(); - if(result.type == ink::runtime::value::Type::String) { - const char* src = result.v_string; - size_t len = string_handler::length(src); - char* buffer = allocate(strings, len + 1); - char* ptr = buffer; - while(*src != '\0') - *(ptr++) = *(src++); - *ptr = 0; - result.v_string = buffer; - } - push(stack, result); + } + + // calls the underlying delegate using arguments on the stack + virtual void call( + basic_eval_stack* stack, size_t length, string_table& strings, list_table& lists + ) override + { + constexpr bool RET_VOID + = is_same::return_type, void>::value; + // Create variable array + TArray variables; + for (size_t i = 0; i < length; i++) { + variables.Add(pop(stack, lists)); + } + if constexpr (RET_VOID) { + invocableDelegate.Execute(variables); + push(stack, 0); + } else { + + auto ret = invocableDelegate.Execute(variables); + ink::runtime::value result = ret.to_value(); + if (result.type == ink::runtime::value::Type::String) { + const char* src = result.v_string; + size_t len = string_handler::length(src); + char* buffer = allocate(strings, len + 1); + char* ptr = buffer; + while (*src != '\0') + *(ptr++) = *(src++); + *ptr = 0; + result.v_string = buffer; } + push(stack, result); } - private: - D invocableDelegate; - }; + } + +private: + D invocableDelegate; +}; #endif -} +} // namespace ink::runtime::internal diff --git a/inkcpp/include/runner.h b/inkcpp/include/runner.h index 5863e8ec..82e1e0b1 100644 --- a/inkcpp/include/runner.h +++ b/inkcpp/include/runner.h @@ -6,254 +6,262 @@ #include "types.h" #ifdef INK_ENABLE_UNREAL -#include "Containers/UnrealString.h" +# include "Containers/UnrealString.h" #endif namespace ink::runtime { - class choice; +class choice; - /** - * A runner to execute ink script from a story. - * - * An independant runner which can execute ink script from a - * story independant of other runners. Think of the ink story - * object like an executable file and the runners as 'threads' - * (not to be confused with ink threads, which are a language - * feature). Runners track their own instruction pointer, choice - * list, temporary variables, callstack, etc. They can either - * be started with their own globals store, or can share - * one with other runner instances. - * @see globals - * @see story - */ - class runner_interface - { - public: - virtual ~runner_interface(){}; +/** + * A runner to execute ink script from a story. + * + * An independant runner which can execute ink script from a + * story independant of other runners. Think of the ink story + * object like an executable file and the runners as 'threads' + * (not to be confused with ink threads, which are a language + * feature). Runners track their own instruction pointer, choice + * list, temporary variables, callstack, etc. They can either + * be started with their own globals store, or can share + * one with other runner instances. + * @see globals + * @see story + */ +class runner_interface +{ +public: + virtual ~runner_interface(){}; #pragma region Interface Methods - /** - * Sets seed for PRNG used in runner. - * Else runner is started with the current time as seed. - * @param seed seed to use for PRNG - */ - virtual void set_rng_seed(uint32_t seed) = 0; + /** + * Sets seed for PRNG used in runner. + * Else runner is started with the current time as seed. + * @param seed seed to use for PRNG + */ + virtual void set_rng_seed(uint32_t seed) = 0; - /** - * Moves the runner to the specified path - * - * Clears any execution context and moves the runner - * to the content at the specified path. - * - * @param path path to search and move execution to - * @return If the path was found - */ - virtual bool move_to(hash_t path) = 0; + /** + * Moves the runner to the specified path + * + * Clears any execution context and moves the runner + * to the content at the specified path. + * + * @param path path to search and move execution to + * @return If the path was found + */ + virtual bool move_to(hash_t path) = 0; - /** - * Can the runner continue? - * - * Checks if the runner can continue execution. If it - * can't, we are either at a choice or are out of content. - * @see continue - * @see has_choices - * - * @return Can continue be called - */ - virtual bool can_continue() const = 0; + /** + * Can the runner continue? + * + * Checks if the runner can continue execution. If it + * can't, we are either at a choice or are out of content. + * @see continue + * @see has_choices + * + * @return Can continue be called + */ + virtual bool can_continue() const = 0; #ifdef INK_ENABLE_CSTD - /** - * Continue execution until the next newline, then allocate a c-style - * string with the output. This allocated string is now the callers - * responsibility and it should be deleted. - * - * @return allocated c-style string with the output of a single line of execution - */ - virtual char* getline_alloc() = 0; + /** + * Continue execution until the next newline, then allocate a c-style + * string with the output. This allocated string is now the callers + * responsibility and it should be deleted. + * + * @return allocated c-style string with the output of a single line of execution + */ + virtual char* getline_alloc() = 0; #endif - /** - * @brief creates a snapshot containing the runner, globals and all other runners connected to the globals. - * @sa story::new_runner_from_snapshot, story::new_globals_from_snapshot - */ - virtual snapshot* create_snapshot() const = 0; + /** + * @brief creates a snapshot containing the runner, globals and all other runners connected to the + * globals. + * @sa story::new_runner_from_snapshot, story::new_globals_from_snapshot + */ + virtual snapshot* create_snapshot() const = 0; #ifdef INK_ENABLE_STL - /** - * Gets the next line of output using C++ STL string. - * - * Continue execution until the next newline, then return the output as - * an STL C++ std::string. Requires INK_ENABLE_STL - * - * @return std::string with the next line of output - */ - virtual std::string getline() = 0; + /** + * Gets the next line of output using C++ STL string. + * + * Continue execution until the next newline, then return the output as + * an STL C++ std::string. Requires INK_ENABLE_STL + * + * @return std::string with the next line of output + */ + virtual std::string getline() = 0; - /** - * Gets the next line of output using C++ STL string. - * - * Continue execution until the next newline, then return the output to - * an STL C++ std::ostream. Requires INK_ENABLE_STL - */ - virtual void getline(std::ostream&) = 0; + /** + * Gets the next line of output using C++ STL string. + * + * Continue execution until the next newline, then return the output to + * an STL C++ std::ostream. Requires INK_ENABLE_STL + */ + virtual void getline(std::ostream&) = 0; - /** - * Gets all the text until the next choice or end - * - * Continue execution until the next choice or the story ends, - * then return the output as an STL C++ std::string. - * Requires INK_ENABLE_STL - * - * @return std::string with the next line of output - */ - virtual std::string getall() = 0; + /** + * Gets all the text until the next choice or end + * + * Continue execution until the next choice or the story ends, + * then return the output as an STL C++ std::string. + * Requires INK_ENABLE_STL + * + * @return std::string with the next line of output + */ + virtual std::string getall() = 0; - /** - * Gets all the text until the next choice or end - * - * Continue execution until the next choice or the story ends, - * then return the output to an STL C++ std::ostream. - * Requires INK_ENABLE_STL - */ - virtual void getall(std::ostream&) = 0; + /** + * Gets all the text until the next choice or end + * + * Continue execution until the next choice or the story ends, + * then return the output to an STL C++ std::ostream. + * Requires INK_ENABLE_STL + */ + virtual void getall(std::ostream&) = 0; #endif #ifdef INK_ENABLE_UNREAL - /** - * Gets the next line of output using unreal string allocation - * - * Continue execution until the next newline, then return the output as - * an Unreal FString. Requires INK_ENABLE_UNREAL - * - * @return FString with the next line of output - */ - virtual FString getline() = 0; + /** + * Gets the next line of output using unreal string allocation + * + * Continue execution until the next newline, then return the output as + * an Unreal FString. Requires INK_ENABLE_UNREAL + * + * @return FString with the next line of output + */ + virtual FString getline() = 0; #endif - /** - * Choice iterator. - * - * Iterates over choices the runner is currently facing. - * - * @see end - * @return constant iterator to the first choice - */ - virtual const choice* begin() const = 0; + /** + * Choice iterator. + * + * Iterates over choices the runner is currently facing. + * + * @see end + * @return constant iterator to the first choice + */ + virtual const choice* begin() const = 0; + + /** + * Terminal choice iterator. + * + * Pointer past the last choice the runner is currently facing. + * + * @see begin + * @return end iterator + */ + virtual const choice* end() const = 0; - /** - * Terminal choice iterator. - * - * Pointer past the last choice the runner is currently facing. - * - * @see begin - * @return end iterator - */ - virtual const choice* end() const = 0; + /** + * Make a choice. + * + * Takes the choice at the given index and moves the instruction + * pointer to that branch. + * + * @param index index of the choice to make + */ + virtual void choose(size_t index) = 0; - /** - * Make a choice. - * - * Takes the choice at the given index and moves the instruction - * pointer to that branch. - * - * @param index index of the choice to make - */ - virtual void choose(size_t index) = 0; + /** check if since last choice selection tags have been added */ + virtual bool has_tags() const = 0; + /** return the number of tags accumulated since last choice + * order of tags wont change, and new are added at the end */ + virtual size_t num_tags() const = 0; + virtual const char* get_tag(size_t index) const = 0; - /** check if since last choice selection tags have been added */ - virtual bool has_tags() const = 0; - /** return the number of tags accumulated since last choice - * order of tags wont change, and new are added at the end */ - virtual size_t num_tags() const = 0; - virtual const char* get_tag(size_t index) const = 0; +protected: + // internal bind implementation. not for calling. + virtual void internal_bind(hash_t name, internal::function_base* function) = 0; - protected: - // internal bind implementation. not for calling. - virtual void internal_bind(hash_t name, internal::function_base* function) = 0; - public: - /** - * Binds an external callable to the runtime - * - * Given a name and a callable object, register this function - * to be called back from the ink runtime. - * - * @param name name hash - * @param function callable - */ - template - inline void bind(hash_t name, F function) - { - internal_bind(name, new internal::function(function)); - } +public: + /** + * Binds an external callable to the runtime + * + * Given a name and a callable object, register this function + * to be called back from the ink runtime. + * + * beside an exact signature match, the function can also have one of the following signatures: + * + void(size_t argl, const ink::runtime::value* argv) + * + ink::runtime::value(size_t argl, const ink::runtime::value* argv) + * this provides a generic way to bind functions with abitrary length + * @param name name hash + * @param function callable + */ + template + inline void bind(hash_t name, F function) + { + internal_bind(name, new internal::function(function)); + } - /** - * Binds an external callable to the runtime - * - * Given a name and a callable object, register this function - * to be called back from the ink runtime. - * - * @param name name string - * @param function callable - */ - template - inline void bind(const char* name, F function) - { - bind(ink::hash_string(name), function); - } + /** + * Binds an external callable to the runtime + * + * Given a name and a callable object, register this function + * to be called back from the ink runtime. + * + * @param name name string + * @param function callable + */ + template + inline void bind(const char* name, F function) + { + bind(ink::hash_string(name), function); + } #ifdef INK_ENABLE_UNREAL - template - void bind_delegate(hash_t name, D functionDelegate) - { - internal_bind(name, new internal::function_array_delegate(functionDelegate)); - } + template + void bind_delegate(hash_t name, D functionDelegate) + { + internal_bind(name, new internal::function_array_delegate(functionDelegate)); + } #endif -#pragma endregion +#pragma endregion #pragma region Convenience Methods - /** - * Shortcut for checking if the runner can continue. - * - * @see can_continue - */ - inline operator bool() const { return can_continue(); } - /** - * Checks if we're currently facing any choices - * - * @return are there any choices available - */ - inline bool has_choices() const { return begin() != end(); } + /** + * Shortcut for checking if the runner can continue. + * + * @see can_continue + */ + inline operator bool() const { return can_continue(); } - /** - * Returns the number of choices currently available - * - * @return number of choices - */ - size_t num_choices() const; + /** + * Checks if we're currently facing any choices + * + * @return are there any choices available + */ + inline bool has_choices() const { return begin() != end(); } - /** - * Gets a choice - * - * Returns the choice object at a given index - * - * @see num_choices - * @param index index of the choice to access - * @return choice object with info on the choice - */ - const choice* get_choice(size_t index) const; + /** + * Returns the number of choices currently available + * + * @return number of choices + */ + size_t num_choices() const; + + /** + * Gets a choice + * + * Returns the choice object at a given index + * + * @see num_choices + * @param index index of the choice to access + * @return choice object with info on the choice + */ + const choice* get_choice(size_t index) const; + + /** + * Shorcut for accessing a choice + * + * @see get_choice + * @param index index of the choice to access + * @return choice object with info on the choice + */ + inline const choice* operator[](size_t index) { return get_choice(index); } - /** - * Shorcut for accessing a choice - * - * @see get_choice - * @param index index of the choice to access - * @return choice object with info on the choice - */ - inline const choice* operator[](size_t index) { return get_choice(index); } #pragma endregion - }; -} +}; +} // namespace ink::runtime diff --git a/inkcpp/runner_impl.cpp b/inkcpp/runner_impl.cpp index 22881d50..26a62585 100644 --- a/inkcpp/runner_impl.cpp +++ b/inkcpp/runner_impl.cpp @@ -392,7 +392,9 @@ runner_impl::runner_impl(const story_impl* data, globals global) runner_impl::~runner_impl() { // unregister with globals - _globals->remove_runner(this); + if (_globals.is_valid()) { + _globals->remove_runner(this); + } } #ifdef INK_ENABLE_STL diff --git a/inkcpp_cl/inkcpp_cl.cpp b/inkcpp_cl/inkcpp_cl.cpp index ab429508..a00ababe 100644 --- a/inkcpp_cl/inkcpp_cl.cpp +++ b/inkcpp_cl/inkcpp_cl.cpp @@ -155,16 +155,16 @@ int main(int argc, const char** argv) while (true) { while (thread->can_continue()) { std::cout << thread->getline(); - } - if (thread->has_tags()) { - std::cout << "# tags: "; - for (int i = 0; i < thread->num_tags(); ++i) { - if (i != 0) { - std::cout << ", "; + if (thread->has_tags()) { + std::cout << "# tags: "; + for (int i = 0; i < thread->num_tags(); ++i) { + if (i != 0) { + std::cout << ", "; + } + std::cout << thread->get_tag(i); } - std::cout << thread->get_tag(i); + std::cout << std::endl; } - std::cout << std::endl; } if (thread->has_choices()) { // Extra end line diff --git a/inkcpp_py/CMakeLists.txt b/inkcpp_py/CMakeLists.txt new file mode 100644 index 00000000..1d9f4ce1 --- /dev/null +++ b/inkcpp_py/CMakeLists.txt @@ -0,0 +1,19 @@ +add_subdirectory(pybind11) +pybind11_add_module(inkcpp_py module.cpp) +target_compile_definitions(inkcpp_py PRIVATE VERSION_INFO=${VERSION}) + +target_link_libraries(inkcpp_py PUBLIC inkcpp inkcpp_compiler inkcpp_shared) + + +# For https://en.cppreference.com/w/cpp/filesystem#Notes +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1") + target_link_libraries(inkcpp_py PRIVATE stdc++fs) + endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0") + target_link_libraries(inkcpp_py PRIVATE stdc++fs) + endif() +endif() + +install(TARGETS inkcpp_py DESTINATION . COMPONENT py EXCLUDE_FROM_ALL) diff --git a/inkcpp_py/example.py b/inkcpp_py/example.py new file mode 100755 index 00000000..db012da7 --- /dev/null +++ b/inkcpp_py/example.py @@ -0,0 +1,84 @@ +#!/bin/python + +import inkcpp_py +import sys +import os + +story_ink = 'unreal_example.ink' +story_json = 'unreal_example.ink.json' +story_bin = 'unreal_example.bin' + + +# convert .ink / .json file to .bin file +if not os.path.exists(story_json): + os.system('inklecate {}'.format(story_ink)) +if not os.path.exists(story_bin): + inkcpp_py.compile_json(story_json, story_bin) + +# load story and maybe snapshot +story = inkcpp_py.Story.from_file(story_bin) +if len(sys.argv) > 1: + snap = inkcpp_py.Snapshot.from_file(sys.argv[1]) + globals = story.new_globals_from_snapshot(snap) + runner = story.new_runner_from_snapshot(snap, globals) +else: + globals = story.new_globals() + runner = story.new_runner(globals) + +# access global variables +print("Date: ", globals.date) +globals.date = inkcpp_py.Value("17.12.2023") +bg = globals.background.as_list(); +print(bg) +bg.add('b') +print(bg) +bg.remove('a') +print(bg) +globals.background = inkcpp_py.Value(bg) + +# observer examples +def ob_ping(): + print("chang^-^") +def ob_value(x): + print("now: ", x) +def ob_delta(x, y): + print("from:",y,"to:",x) + +globals.observe_ping("brightness", ob_ping) +globals.observe_value("brightness", ob_value) +globals.observe_delta("brightness", ob_delta) + + +# external function with no input, but return value +def greeting(a): + return inkcpp_py.Value("Tach") +runner.bind("GetGreeting", greeting) + +# external function with no return value +def brightness(args): + print("Set Brightness: ", args[0]) +runner.bind_void("SetBrightness", brightness) + +# simple story stepper +while True: + while runner.can_continue(): + print(runner.getline()) + print("# tags: ", end="") + print(', '.join(runner.tags())) + if runner.has_choices(): + print() + index = 1 + for c in runner: + print(str(index) + ": " + c.text()) + print("\t"+', '.join(c.tags())) + index += 1 + index = int(input('Select choice to continue: ')) + if index == -1: + snap = runner.create_snapshot(); + snap.write_to_file("story.snap"); + break + else: + runner.choose(index - 1) + else: + break +print("Finish") diff --git a/inkcpp_py/module.cpp b/inkcpp_py/module.cpp new file mode 100644 index 00000000..7bde7e27 --- /dev/null +++ b/inkcpp_py/module.cpp @@ -0,0 +1,288 @@ +#include "compilation_results.h" +#include "story_ptr.h" +#include "types.h" +#include +#include +#include +#include +#include +#include +#include + +namespace py = pybind11; + +#include +#include +#include +#include +#include +#include + +#include +#include + +using runner = ink::runtime::runner_interface; +using runner_ptr = ink::runtime::runner; +using globals = ink::runtime::globals_interface; +using globals_ptr = ink::runtime::globals; +using story = ink::runtime::story; +using choice = ink::runtime::choice; +using value = ink::runtime::value; +using list = ink::runtime::list_interface; +using snapshot = ink::runtime::snapshot; + +PYBIND11_DECLARE_HOLDER_TYPE(T, ink::runtime::story_ptr); + +struct StringValueWrap : public value { + StringValueWrap(const std::string& s) + : str{s} + { + value::type = value::Type::String; + value::v_string = str.c_str(); + } + + ~StringValueWrap() { std::cout << "death" << std::endl; } + + std::string str; +}; + +std::string list_to_str(const list& list) +{ + std::stringstream out; + out << "["; + bool first = true; + for (const auto& flag : list) { + if (first) { + first = false; + } else { + out << ", "; + } + out << flag; + } + out << "]"; + return out.str(); +} + +PYBIND11_MODULE(inkcpp_py, m) +{ + m.doc() + = "Python bindings for InkCPP https://github.com/JBenda/inkcpp"; // optional module docstring + + py::class_(m, "Story") + .def("from_file", &story::from_file, "Creates a new story from a .bin file") + .def("new_globals", &story::new_globals, "creates new globals store for the current story") + .def("new_runner", [](story& self) { return self.new_runner(); }) + .def("new_runner", &story::new_runner, "creates a new runner for the current story") + .def("new_globals_from_snapshot", &story::new_globals_from_snapshot) + .def("new_runner_from_snapshot", &story::new_runner_from_snapshot) + .def( + "new_runner_from_snapshot", [](story& self, const snapshot& snap, globals_ptr store + ) { return self.new_runner_from_snapshot(snap, store); } + ) + .def( + "new_runner_from_snapshot", + [](story& self, const snapshot& snap, globals_ptr store, unsigned idx) { + return self.new_runner_from_snapshot(snap, store, idx); + } + ); + + py::class_(m, "Globals") + .def( + "create_snapshot", &globals::create_snapshot, + "Creates a snapshot from the current state for later usage" + ) + .def( + "observe_ping", + [](globals& self, const char* name, std::function f) { self.observe(name, f); }, + "Get a ping each time the observed variable changes" + ) + .def( + "observe_value", + [](globals& self, const char* name, std::function f) { + self.observe(name, f); + }, + "Get a call with new value each time the variable changes" + ) + .def( + "observe_delta", + [](globals& self, const char* name, + std::function)> f) { + self.observe(name, f); + }, + "Get a call with the new and old value (this could be None) each time the variable " + "changes" + ) + .def( + "__getattr__", + [](const globals& self, const std::string& key) { + auto res = self.get(key.c_str()); + if (! res.has_value()) { + throw py::key_error(std::string("No global variable with name '") + key + "' found"); + } + return res.value(); + }, + "Access global varible if exists, if not throws an key_error" + ) + .def("__setattr__", [](globals& self, const std::string& key, const value& val) { + if (! self.set(key.c_str(), val)) { + throw py::key_error( + std::string("No global variable with name '") + key + + "' found you are trying to override a non string variable with a string" + ); + } + }); + + py::class_>( + m, "List", + "Allows reading and editing inkcpp lists. !Only valid until next choose ore getline a runner " + "referncing the corresponding global" + ) + .def("add", &list::add, "Add flag to list") + .def("remove", &list::remove, "Remove flag from list") + .def("contains", &list::contains, "Check if list contains the given flag") + .def("__str__", &list_to_str); + + + py::class_ py_value(m, "Value", "A Value of a Ink Variable"); + py_value.def_readonly("type", &value::type, "Type contained in value"); + py_value.def(py::init<>()); + py_value.def(py::init()); + py_value.def(py::init()); + py_value.def(py::init()); + py_value.def(py::init()); + py_value.def(py::init()); + py_value.def(py::init([](const std::string& str) { return new StringValueWrap(str); })); + py_value.def( + "as_list", + [](const value& self) { + if (self.type != value::Type::List) { + throw py::attribute_error("Try to access list of non list value"); + } + return self.v_list; + }, + py::return_value_policy::reference + ); + py_value.def("__str__", [](const value& self) { + switch (self.type) { + case value::Type::Bool: return std::string(self.v_bool ? "true" : "false"); + case value::Type::Uint32: return std::to_string(self.v_uint32); + case value::Type::Int32: return std::to_string(self.v_int32); + case value::Type::String: return std::string(self.v_string); + case value::Type::Float: return std::to_string(self.v_float); + case value::Type::List: { + return list_to_str(*self.v_list); + } + } + throw py::attribute_error("value is in an invalid state"); + }); + + py::enum_(m, "Type") + .value("Bool", value::Type::Bool) + .value("Uint32", value::Type::Uint32) + .value("Int32", value::Type::Int32) + .value("String", value::Type::String) + .value("Float", value::Type::Float) + .value("List", value::Type::List) + .export_values(); + + py::class_(m, "Runner") + .def( + "create_snapshot", &runner::create_snapshot, + "Creates a snapshot from the current state for later usage" + ) + .def("can_continue", &runner::can_continue, "check if there is content left in story") + .def("getline", static_cast(&runner::getline)) + .def("has_tags", &runner::has_tags, "Where there tags since last getline") + .def("num_tags", &runner::num_tags, "Number of tags currently stored") + .def( + "get_tag", &runner::get_tag, "Get Tag currently stored at position i", + py::return_value_policy::reference_internal + ) + .def( + "tags", + [](const runner& self) { + std::vector tags(self.num_tags()); + for (size_t i = 0; i < self.num_tags(); ++i) { + tags[i] = self.get_tag(i); + } + return tags; + }, + "Get all current assigned tags" + ) + .def( + "has_choices", &runner::has_choices, + "Check if there is at least one open choice at the moment." + ) + .def( + "get_choice", &runner::get_choice, "Get current choice at index", + py::return_value_policy::reference_internal + ) + .def("num_choices", &runner::num_choices, "Number of current open choices") + .def( + "__iter__", + [](const runner& self) { return py::make_iterator(self.begin(), self.end()); }, + py::keep_alive<0, 1>() + ) + .def("choose", &runner::choose, "Select a choice to continue") + .def( + "bind_void", + [](runner& self, const char* function_name, std::function)> f) { + self.bind(function_name, [f](size_t len, const value* vals) { + std::vector args(vals, vals + len); + f(args); + }); + }, + "Bind function which void result" + ) + .def( + "bind", + [](runner& self, const char* function_name, std::function)> f) { + self.bind(function_name, [f](size_t len, const value* vals) { + std::vector args(vals, vals + len); + return f(args); + }); + }, + "Bind a function with return value" + ); + py::class_(m, "Choice") + .def("text", &choice::text, "Get choice printable content") + .def("has_tags", &choice::has_tags, "if choices is tagged?") + .def("num_tags", &choice::num_tags, "Number of tags assigned to choice") + .def( + "get_tag", &choice::get_tag, "Get tag at index", + py::return_value_policy::reference_internal + ) + .def( + "tags", + [](const choice& self) { + std::vector tags(self.num_tags()); + for (size_t i = 0; i < self.num_tags(); ++i) { + tags[i] = self.get_tag(i); + } + return tags; + }, + "Get all current assinged tags" + ); + py::class_( + m, "Snapshot", "Globals and all assoziatet runner stored for later restoration" + ) + .def("num_runners", &snapshot::num_runners, "Number of different runners stored in snapshot") + .def("write_to_file", &snapshot::write_to_file, "Store snapshot in file") + .def("from_file", &snapshot::from_file, "Load snapshot from file"); + m.def( + "compile_json", + [](const char* input_file_name, const char* output_filen_ame) { + ink::compiler::compilation_results results; + ink::compiler::run(input_file_name, output_filen_ame, &results); + if (! results.errors.empty()) { + std::string str; + for (auto& error : results.errors) { + str += error; + str += '\n'; + } + throw py::value_error(str); + } + }, + "Converts a story.json file to a story.bin file used by inkcpp" + ); +} diff --git a/inkcpp_py/pybind11 b/inkcpp_py/pybind11 new file mode 160000 index 00000000..8b03ffa7 --- /dev/null +++ b/inkcpp_py/pybind11 @@ -0,0 +1 @@ +Subproject commit 8b03ffa7c06cd9c8a38297b1c8923695d1ff1b07 diff --git a/inkcpp_py/sources.json b/inkcpp_py/sources.json new file mode 100644 index 00000000..71069dce --- /dev/null +++ b/inkcpp_py/sources.json @@ -0,0 +1,362 @@ +[ +"shared/", +"shared/CMakeLists.txt", +"shared/private", +"shared/private/command.h", +"shared/private/header.h", +"shared/public", +"shared/public/config.h", +"shared/public/system.h", +"shared/public/version.h", +"inkcpp/", +"inkcpp/CMakeLists.txt", +"inkcpp/array.h", +"inkcpp/avl_array.h", +"inkcpp/casting.h", +"inkcpp/choice.cpp", +"inkcpp/collections", +"inkcpp/collections/restorable.cpp", +"inkcpp/collections/restorable.h", +"inkcpp/container_operations.cpp", +"inkcpp/container_operations.h", +"inkcpp/executioner.h", +"inkcpp/functional.cpp", +"inkcpp/functions.cpp", +"inkcpp/functions.h", +"inkcpp/globals_impl.cpp", +"inkcpp/globals_impl.h", +"inkcpp/header.cpp", +"inkcpp/include", +"inkcpp/include/choice.h", +"inkcpp/include/functional.h", +"inkcpp/include/globals.h", +"inkcpp/include/list.h", +"inkcpp/include/runner.h", +"inkcpp/include/snapshot.h", +"inkcpp/include/story.h", +"inkcpp/include/story_ptr.h", +"inkcpp/include/traits.h", +"inkcpp/include/types.h", +"inkcpp/list_impl.cpp", +"inkcpp/list_impl.h", +"inkcpp/list_operations.cpp", +"inkcpp/list_operations.h", +"inkcpp/list_table.cpp", +"inkcpp/list_table.h", +"inkcpp/numeric_operations.cpp", +"inkcpp/numeric_operations.h", +"inkcpp/operation_bases.h", +"inkcpp/operations.h", +"inkcpp/output.cpp", +"inkcpp/output.h", +"inkcpp/platform.h", +"inkcpp/random.h", +"inkcpp/runner_impl.cpp", +"inkcpp/runner_impl.h", +"inkcpp/simple_restorable_stack.h", +"inkcpp/snapshot_impl.cpp", +"inkcpp/snapshot_impl.h", +"inkcpp/snapshot_interface.h", +"inkcpp/stack.cpp", +"inkcpp/stack.h", +"inkcpp/story_impl.cpp", +"inkcpp/story_impl.h", +"inkcpp/story_ptr.cpp", +"inkcpp/string_operations.cpp", +"inkcpp/string_operations.h", +"inkcpp/string_table.cpp", +"inkcpp/string_table.h", +"inkcpp/string_utils.h", +"inkcpp/system.cpp", +"inkcpp/tuple.hpp", +"inkcpp/value.cpp", +"inkcpp/value.h", +"inkcpp_compiler/", +"inkcpp_compiler/CMakeLists.txt", +"inkcpp_compiler/binary_emitter.cpp", +"inkcpp_compiler/binary_emitter.h", +"inkcpp_compiler/binary_stream.cpp", +"inkcpp_compiler/binary_stream.h", +"inkcpp_compiler/command.cpp", +"inkcpp_compiler/compiler.cpp", +"inkcpp_compiler/emitter.cpp", +"inkcpp_compiler/emitter.h", +"inkcpp_compiler/include", +"inkcpp_compiler/include/compilation_results.h", +"inkcpp_compiler/include/compiler.h", +"inkcpp_compiler/json.hpp", +"inkcpp_compiler/json_compiler.cpp", +"inkcpp_compiler/json_compiler.h", +"inkcpp_compiler/list_data.cpp", +"inkcpp_compiler/list_data.h", +"inkcpp_compiler/reporter.cpp", +"inkcpp_compiler/reporter.h", +"inkcpp_compiler/text_emitter.cpp", +"inkcpp_compiler/text_emitter.h", +"inkcpp_py/", +"inkcpp_py/CMakeLists.txt", +"inkcpp_py/example.py", +"inkcpp_py/module.cpp", +"inkcpp_py/pybind11", +"inkcpp_py/pybind11/CMakeLists.txt", +"inkcpp_py/pybind11/LICENSE", +"inkcpp_py/pybind11/MANIFEST.in", +"inkcpp_py/pybind11/README.rst", +"inkcpp_py/pybind11/SECURITY.md", +"inkcpp_py/pybind11/docs", +"inkcpp_py/pybind11/docs/Doxyfile", +"inkcpp_py/pybind11/docs/Makefile", +"inkcpp_py/pybind11/docs/_static", +"inkcpp_py/pybind11/docs/_static/css", +"inkcpp_py/pybind11/docs/_static/css/custom.css", +"inkcpp_py/pybind11/docs/advanced", +"inkcpp_py/pybind11/docs/advanced/cast", +"inkcpp_py/pybind11/docs/advanced/cast/chrono.rst", +"inkcpp_py/pybind11/docs/advanced/cast/custom.rst", +"inkcpp_py/pybind11/docs/advanced/cast/eigen.rst", +"inkcpp_py/pybind11/docs/advanced/cast/functional.rst", +"inkcpp_py/pybind11/docs/advanced/cast/index.rst", +"inkcpp_py/pybind11/docs/advanced/cast/overview.rst", +"inkcpp_py/pybind11/docs/advanced/cast/stl.rst", +"inkcpp_py/pybind11/docs/advanced/cast/strings.rst", +"inkcpp_py/pybind11/docs/advanced/classes.rst", +"inkcpp_py/pybind11/docs/advanced/embedding.rst", +"inkcpp_py/pybind11/docs/advanced/exceptions.rst", +"inkcpp_py/pybind11/docs/advanced/functions.rst", +"inkcpp_py/pybind11/docs/advanced/misc.rst", +"inkcpp_py/pybind11/docs/advanced/pycpp", +"inkcpp_py/pybind11/docs/advanced/pycpp/index.rst", +"inkcpp_py/pybind11/docs/advanced/pycpp/numpy.rst", +"inkcpp_py/pybind11/docs/advanced/pycpp/object.rst", +"inkcpp_py/pybind11/docs/advanced/pycpp/utilities.rst", +"inkcpp_py/pybind11/docs/advanced/smart_ptrs.rst", +"inkcpp_py/pybind11/docs/basics.rst", +"inkcpp_py/pybind11/docs/benchmark.py", +"inkcpp_py/pybind11/docs/benchmark.rst", +"inkcpp_py/pybind11/docs/changelog.rst", +"inkcpp_py/pybind11/docs/classes.rst", +"inkcpp_py/pybind11/docs/cmake", +"inkcpp_py/pybind11/docs/cmake/index.rst", +"inkcpp_py/pybind11/docs/compiling.rst", +"inkcpp_py/pybind11/docs/conf.py", +"inkcpp_py/pybind11/docs/faq.rst", +"inkcpp_py/pybind11/docs/index.rst", +"inkcpp_py/pybind11/docs/installing.rst", +"inkcpp_py/pybind11/docs/limitations.rst", +"inkcpp_py/pybind11/docs/pybind11-logo.png", +"inkcpp_py/pybind11/docs/pybind11_vs_boost_python1.png", +"inkcpp_py/pybind11/docs/pybind11_vs_boost_python1.svg", +"inkcpp_py/pybind11/docs/pybind11_vs_boost_python2.png", +"inkcpp_py/pybind11/docs/pybind11_vs_boost_python2.svg", +"inkcpp_py/pybind11/docs/reference.rst", +"inkcpp_py/pybind11/docs/release.rst", +"inkcpp_py/pybind11/docs/requirements.txt", +"inkcpp_py/pybind11/docs/upgrade.rst", +"inkcpp_py/pybind11/include", +"inkcpp_py/pybind11/include/pybind11", +"inkcpp_py/pybind11/include/pybind11/attr.h", +"inkcpp_py/pybind11/include/pybind11/buffer_info.h", +"inkcpp_py/pybind11/include/pybind11/cast.h", +"inkcpp_py/pybind11/include/pybind11/chrono.h", +"inkcpp_py/pybind11/include/pybind11/common.h", +"inkcpp_py/pybind11/include/pybind11/complex.h", +"inkcpp_py/pybind11/include/pybind11/detail", +"inkcpp_py/pybind11/include/pybind11/detail/class.h", +"inkcpp_py/pybind11/include/pybind11/detail/common.h", +"inkcpp_py/pybind11/include/pybind11/detail/descr.h", +"inkcpp_py/pybind11/include/pybind11/detail/init.h", +"inkcpp_py/pybind11/include/pybind11/detail/internals.h", +"inkcpp_py/pybind11/include/pybind11/detail/type_caster_base.h", +"inkcpp_py/pybind11/include/pybind11/detail/typeid.h", +"inkcpp_py/pybind11/include/pybind11/eigen.h", +"inkcpp_py/pybind11/include/pybind11/eigen", +"inkcpp_py/pybind11/include/pybind11/eigen/common.h", +"inkcpp_py/pybind11/include/pybind11/eigen/matrix.h", +"inkcpp_py/pybind11/include/pybind11/eigen/tensor.h", +"inkcpp_py/pybind11/include/pybind11/embed.h", +"inkcpp_py/pybind11/include/pybind11/eval.h", +"inkcpp_py/pybind11/include/pybind11/functional.h", +"inkcpp_py/pybind11/include/pybind11/gil.h", +"inkcpp_py/pybind11/include/pybind11/iostream.h", +"inkcpp_py/pybind11/include/pybind11/numpy.h", +"inkcpp_py/pybind11/include/pybind11/operators.h", +"inkcpp_py/pybind11/include/pybind11/options.h", +"inkcpp_py/pybind11/include/pybind11/pybind11.h", +"inkcpp_py/pybind11/include/pybind11/pytypes.h", +"inkcpp_py/pybind11/include/pybind11/stl.h", +"inkcpp_py/pybind11/include/pybind11/stl", +"inkcpp_py/pybind11/include/pybind11/stl/filesystem.h", +"inkcpp_py/pybind11/include/pybind11/stl_bind.h", +"inkcpp_py/pybind11/include/pybind11/type_caster_pyobject_ptr.h", +"inkcpp_py/pybind11/noxfile.py", +"inkcpp_py/pybind11/pybind11", +"inkcpp_py/pybind11/pybind11/__init__.py", +"inkcpp_py/pybind11/pybind11/__main__.py", +"inkcpp_py/pybind11/pybind11/_version.py", +"inkcpp_py/pybind11/pybind11/commands.py", +"inkcpp_py/pybind11/pybind11/py.typed", +"inkcpp_py/pybind11/pybind11/setup_helpers.py", +"inkcpp_py/pybind11/pyproject.toml", +"inkcpp_py/pybind11/setup.cfg", +"inkcpp_py/pybind11/setup.py", +"inkcpp_py/pybind11/tests", +"inkcpp_py/pybind11/tests/CMakeLists.txt", +"inkcpp_py/pybind11/tests/conftest.py", +"inkcpp_py/pybind11/tests/constructor_stats.h", +"inkcpp_py/pybind11/tests/cross_module_gil_utils.cpp", +"inkcpp_py/pybind11/tests/cross_module_interleaved_error_already_set.cpp", +"inkcpp_py/pybind11/tests/eigen_tensor_avoid_stl_array.cpp", +"inkcpp_py/pybind11/tests/env.py", +"inkcpp_py/pybind11/tests/extra_python_package", +"inkcpp_py/pybind11/tests/extra_python_package/pytest.ini", +"inkcpp_py/pybind11/tests/extra_python_package/test_files.py", +"inkcpp_py/pybind11/tests/extra_setuptools", +"inkcpp_py/pybind11/tests/extra_setuptools/pytest.ini", +"inkcpp_py/pybind11/tests/extra_setuptools/test_setuphelper.py", +"inkcpp_py/pybind11/tests/local_bindings.h", +"inkcpp_py/pybind11/tests/object.h", +"inkcpp_py/pybind11/tests/pybind11_cross_module_tests.cpp", +"inkcpp_py/pybind11/tests/pybind11_tests.cpp", +"inkcpp_py/pybind11/tests/pybind11_tests.h", +"inkcpp_py/pybind11/tests/pytest.ini", +"inkcpp_py/pybind11/tests/requirements.txt", +"inkcpp_py/pybind11/tests/test_async.cpp", +"inkcpp_py/pybind11/tests/test_async.py", +"inkcpp_py/pybind11/tests/test_buffers.cpp", +"inkcpp_py/pybind11/tests/test_buffers.py", +"inkcpp_py/pybind11/tests/test_builtin_casters.cpp", +"inkcpp_py/pybind11/tests/test_builtin_casters.py", +"inkcpp_py/pybind11/tests/test_call_policies.cpp", +"inkcpp_py/pybind11/tests/test_call_policies.py", +"inkcpp_py/pybind11/tests/test_callbacks.cpp", +"inkcpp_py/pybind11/tests/test_callbacks.py", +"inkcpp_py/pybind11/tests/test_chrono.cpp", +"inkcpp_py/pybind11/tests/test_chrono.py", +"inkcpp_py/pybind11/tests/test_class.cpp", +"inkcpp_py/pybind11/tests/test_class.py", +"inkcpp_py/pybind11/tests/test_cmake_build", +"inkcpp_py/pybind11/tests/test_cmake_build/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_cmake_build/embed.cpp", +"inkcpp_py/pybind11/tests/test_cmake_build/installed_embed", +"inkcpp_py/pybind11/tests/test_cmake_build/installed_embed/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_cmake_build/installed_function", +"inkcpp_py/pybind11/tests/test_cmake_build/installed_function/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_cmake_build/installed_target", +"inkcpp_py/pybind11/tests/test_cmake_build/installed_target/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_cmake_build/main.cpp", +"inkcpp_py/pybind11/tests/test_cmake_build/subdirectory_embed", +"inkcpp_py/pybind11/tests/test_cmake_build/subdirectory_embed/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_cmake_build/subdirectory_function", +"inkcpp_py/pybind11/tests/test_cmake_build/subdirectory_function/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_cmake_build/subdirectory_target", +"inkcpp_py/pybind11/tests/test_cmake_build/subdirectory_target/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_cmake_build/test.py", +"inkcpp_py/pybind11/tests/test_const_name.cpp", +"inkcpp_py/pybind11/tests/test_const_name.py", +"inkcpp_py/pybind11/tests/test_constants_and_functions.cpp", +"inkcpp_py/pybind11/tests/test_constants_and_functions.py", +"inkcpp_py/pybind11/tests/test_copy_move.cpp", +"inkcpp_py/pybind11/tests/test_copy_move.py", +"inkcpp_py/pybind11/tests/test_custom_type_casters.cpp", +"inkcpp_py/pybind11/tests/test_custom_type_casters.py", +"inkcpp_py/pybind11/tests/test_custom_type_setup.cpp", +"inkcpp_py/pybind11/tests/test_custom_type_setup.py", +"inkcpp_py/pybind11/tests/test_docstring_options.cpp", +"inkcpp_py/pybind11/tests/test_docstring_options.py", +"inkcpp_py/pybind11/tests/test_eigen_matrix.cpp", +"inkcpp_py/pybind11/tests/test_eigen_matrix.py", +"inkcpp_py/pybind11/tests/test_eigen_tensor.cpp", +"inkcpp_py/pybind11/tests/test_eigen_tensor.inl", +"inkcpp_py/pybind11/tests/test_eigen_tensor.py", +"inkcpp_py/pybind11/tests/test_embed", +"inkcpp_py/pybind11/tests/test_embed/CMakeLists.txt", +"inkcpp_py/pybind11/tests/test_embed/catch.cpp", +"inkcpp_py/pybind11/tests/test_embed/external_module.cpp", +"inkcpp_py/pybind11/tests/test_embed/test_interpreter.cpp", +"inkcpp_py/pybind11/tests/test_embed/test_interpreter.py", +"inkcpp_py/pybind11/tests/test_embed/test_trampoline.py", +"inkcpp_py/pybind11/tests/test_enum.cpp", +"inkcpp_py/pybind11/tests/test_enum.py", +"inkcpp_py/pybind11/tests/test_eval.cpp", +"inkcpp_py/pybind11/tests/test_eval.py", +"inkcpp_py/pybind11/tests/test_eval_call.py", +"inkcpp_py/pybind11/tests/test_exceptions.cpp", +"inkcpp_py/pybind11/tests/test_exceptions.h", +"inkcpp_py/pybind11/tests/test_exceptions.py", +"inkcpp_py/pybind11/tests/test_factory_constructors.cpp", +"inkcpp_py/pybind11/tests/test_factory_constructors.py", +"inkcpp_py/pybind11/tests/test_gil_scoped.cpp", +"inkcpp_py/pybind11/tests/test_gil_scoped.py", +"inkcpp_py/pybind11/tests/test_iostream.cpp", +"inkcpp_py/pybind11/tests/test_iostream.py", +"inkcpp_py/pybind11/tests/test_kwargs_and_defaults.cpp", +"inkcpp_py/pybind11/tests/test_kwargs_and_defaults.py", +"inkcpp_py/pybind11/tests/test_local_bindings.cpp", +"inkcpp_py/pybind11/tests/test_local_bindings.py", +"inkcpp_py/pybind11/tests/test_methods_and_attributes.cpp", +"inkcpp_py/pybind11/tests/test_methods_and_attributes.py", +"inkcpp_py/pybind11/tests/test_modules.cpp", +"inkcpp_py/pybind11/tests/test_modules.py", +"inkcpp_py/pybind11/tests/test_multiple_inheritance.cpp", +"inkcpp_py/pybind11/tests/test_multiple_inheritance.py", +"inkcpp_py/pybind11/tests/test_numpy_array.cpp", +"inkcpp_py/pybind11/tests/test_numpy_array.py", +"inkcpp_py/pybind11/tests/test_numpy_dtypes.cpp", +"inkcpp_py/pybind11/tests/test_numpy_dtypes.py", +"inkcpp_py/pybind11/tests/test_numpy_vectorize.cpp", +"inkcpp_py/pybind11/tests/test_numpy_vectorize.py", +"inkcpp_py/pybind11/tests/test_opaque_types.cpp", +"inkcpp_py/pybind11/tests/test_opaque_types.py", +"inkcpp_py/pybind11/tests/test_operator_overloading.cpp", +"inkcpp_py/pybind11/tests/test_operator_overloading.py", +"inkcpp_py/pybind11/tests/test_pickling.cpp", +"inkcpp_py/pybind11/tests/test_pickling.py", +"inkcpp_py/pybind11/tests/test_pytypes.cpp", +"inkcpp_py/pybind11/tests/test_pytypes.py", +"inkcpp_py/pybind11/tests/test_sequences_and_iterators.cpp", +"inkcpp_py/pybind11/tests/test_sequences_and_iterators.py", +"inkcpp_py/pybind11/tests/test_smart_ptr.cpp", +"inkcpp_py/pybind11/tests/test_smart_ptr.py", +"inkcpp_py/pybind11/tests/test_stl.cpp", +"inkcpp_py/pybind11/tests/test_stl.py", +"inkcpp_py/pybind11/tests/test_stl_binders.cpp", +"inkcpp_py/pybind11/tests/test_stl_binders.py", +"inkcpp_py/pybind11/tests/test_tagbased_polymorphic.cpp", +"inkcpp_py/pybind11/tests/test_tagbased_polymorphic.py", +"inkcpp_py/pybind11/tests/test_thread.cpp", +"inkcpp_py/pybind11/tests/test_thread.py", +"inkcpp_py/pybind11/tests/test_type_caster_pyobject_ptr.cpp", +"inkcpp_py/pybind11/tests/test_type_caster_pyobject_ptr.py", +"inkcpp_py/pybind11/tests/test_union.cpp", +"inkcpp_py/pybind11/tests/test_union.py", +"inkcpp_py/pybind11/tests/test_unnamed_namespace_a.cpp", +"inkcpp_py/pybind11/tests/test_unnamed_namespace_a.py", +"inkcpp_py/pybind11/tests/test_unnamed_namespace_b.cpp", +"inkcpp_py/pybind11/tests/test_unnamed_namespace_b.py", +"inkcpp_py/pybind11/tests/test_vector_unique_ptr_member.cpp", +"inkcpp_py/pybind11/tests/test_vector_unique_ptr_member.py", +"inkcpp_py/pybind11/tests/test_virtual_functions.cpp", +"inkcpp_py/pybind11/tests/test_virtual_functions.py", +"inkcpp_py/pybind11/tests/valgrind-numpy-scipy.supp", +"inkcpp_py/pybind11/tests/valgrind-python.supp", +"inkcpp_py/pybind11/tools", +"inkcpp_py/pybind11/tools/FindCatch.cmake", +"inkcpp_py/pybind11/tools/FindEigen3.cmake", +"inkcpp_py/pybind11/tools/FindPythonLibsNew.cmake", +"inkcpp_py/pybind11/tools/JoinPaths.cmake", +"inkcpp_py/pybind11/tools/check-style.sh", +"inkcpp_py/pybind11/tools/cmake_uninstall.cmake.in", +"inkcpp_py/pybind11/tools/codespell_ignore_lines_from_errors.py", +"inkcpp_py/pybind11/tools/libsize.py", +"inkcpp_py/pybind11/tools/make_changelog.py", +"inkcpp_py/pybind11/tools/pybind11.pc.in", +"inkcpp_py/pybind11/tools/pybind11Common.cmake", +"inkcpp_py/pybind11/tools/pybind11Config.cmake.in", +"inkcpp_py/pybind11/tools/pybind11NewTools.cmake", +"inkcpp_py/pybind11/tools/pybind11Tools.cmake", +"inkcpp_py/pybind11/tools/pyproject.toml", +"inkcpp_py/pybind11/tools/setup_global.py.in", +"inkcpp_py/pybind11/tools/setup_main.py.in", +"inkcpp_py/unreal_example.ink", +"inkcpp_py/sources.json" +] diff --git a/inkcpp_py/unreal_example.ink b/inkcpp_py/unreal_example.ink new file mode 100644 index 00000000..e8465ccb --- /dev/null +++ b/inkcpp_py/unreal_example.ink @@ -0,0 +1,71 @@ +->Start +EXTERNAL SetBrightness(x) +=== function SetBrightness(x) === + ~ return + +EXTERNAL GetGreeting() +=== function GetGreeting() === + ~ return "Hey, " + +VAR brightness = 50 + +VAR date = "???" + +LIST background = (a), b, c + +=== Start === +{GetGreeting()}, your personal assistent here. # Story Start +Today is the {date} +Why we don't start with some customisation options: +* [Yes] + -> Settings -> + How The story looks now? much better :) + ->DONE +* [I Don't like you.] + Then Bye + ->DONE + +=== Settings === + += All +Let Start with the color. +->Color-> +The illumination seems off? +->Brightness-> +And now some custom background :) +->Background-> +->-> + += Color +Which color do like? ++ [Magenta # c:mag] + A wired choice ... # setColor_255_0_255 ++ [Cyan # c:cya] + A think this will be a intense experience. # setColor_0_255_255 ++ [Yellow # c:yel] + A delighting decision. # setColor_255_255_0 +- Do You Like your decision? ++ [Yes] ->-> ++ [No] -> Color + += Brightness +Do you like the Brightness level? ++ [A bit more please.] + ~ brightness += 5 ++ [A bit less.] + ~ brightness -= 5 ++ [The settings {is fine| is now to my compliance!}] + ->-> +- ~ SetBrightness(brightness) + ->Brightness + += Background +>> SetBg({background}) +How do you like this image? ++ {background < LIST_MAX(LIST_ALL(background))} [Mhe the next please.] + ~ background++ ++ {background > LIST_MIN(LIST_ALL(background))} [Can you show me the previous please?] + ~ background-- ++ [This is great] + ->-> +- -> Background diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..dd44b0d7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[project] +name = "inkcpp-py" +readme = "README.md" +license = { file = "LICENSE.txt" } +keywords = ["ink", "inkpp", "inklecate"] +dynamic = ["version", "description", "authors", "requires-python"] + +[project.urls] +Repository = "https://github.com/JBenda/inkcpp.git" +Issues = "https://github.com/JBenda/inkcpp/issues" + +[build-system] +requires = [ + "setuptools>=42", + "wheel", + "ninja", + "cmake>=3.16", +] +build-backend = "setuptools.build_meta" + +[tool.mypy] +files = "setup.py" +python_version = "3.7" +strict = true +show_error_codes = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] +warn_unreachable = true + +[[tool.mypy.overrides]] +module = ["ninja"] +ignore_missing_imports = true + + +# [tool.pytest.ini_options] +# minversion = "6.0" +# addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] +# xfail_strict = true +# filterwarnings = [ +# "error", +# "ignore:(ast.Str|Attribute s|ast.NameConstant|ast.Num) is deprecated:DeprecationWarning:_pytest", +# ] +# testpaths = ["tests"] + +# [tool.cibuildwheel] +# test-command = "pytest {project}/tests" +# test-extras = ["test"] +# test-skip = ["*universal2:arm64"] +# Setuptools bug causes collision between pypy and cpython artifacts +# before-build = "rm -rf {project}/build" + +[tool.ruff] +target-version = "py37" + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "I", # isort + "PGH", # pygrep-hooks + "RUF", # Ruff-specific + "UP", # pyupgrade +] diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..9190f702 --- /dev/null +++ b/setup.py @@ -0,0 +1,144 @@ +# based on https://github.com/pybind/cmake_example/blob/master/setup.py + +import os +import re +import sys +import subprocess +import glob +import json +from pathlib import Path +from setuptools import Extension, setup +from setuptools.command.build_ext import build_ext + + +# find shared/** inkcpp/** inkcpp_compiler/** inkcpp_py/** -not -path '*/.*' | sed -e 's/[^ ]*/"\0"/g' -e '/""/d' -e 's/ /,\n/g' +# + "CMakeLists.txt" + +# Convert distutils Windows platform specifiers to CMake -A arguments +PLAT_TO_CMAKE = { + "win32": "Win32", + "win-amd64": "x64", + "win-arm32": "ARM", + "win-arm64": "ARM64", +} + +class CMakeExtension(Extension): + def __init__(self, name: str, sourcedir: str = "") -> None: + src_files = json.load(open('inkcpp_py/sources.json')) + src_files += ["CMakeLists.txt", "Config.cmake.in"] + super().__init__(name, sources=src_files) + self.sourcedir = os.fspath(Path(sourcedir).resolve()) + +class CMakeBuild(build_ext): + def build_extension(self, ext: CMakeExtension) -> None: + # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ + ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) + extdir = ext_fullpath.parent.resolve() + + # Using this requires trailing slash for auto-detection & inclusion of + # auxiliary "native" libs + + debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug + cfg = "Debug" if debug else "Release" + + # CMake lets you override the generator - we need to check this. + # Can be set with Conda-Build, for example. + cmake_generator = os.environ.get("CMAKE_GENERATOR", "") + + # Set Python_EXECUTABLE instead if you use PYBIND11_FINDPYTHON + # EXAMPLE_VERSION_INFO shows you how to pass a value into the C++ code + # from Python. + cmake_args = [ + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", + f"-DPYTHON_EXECUTABLE={sys.executable}", + f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm + f"-DINKCPP_PY=ON", + f"-DWHEEL_BUILD=ON", + ] + build_args = [ + f"--target=inkcpp_py", + ] + # Adding CMake arguments set as environment variable + # (needed e.g. to build for ARM OSx on conda-forge) + if "CMAKE_ARGS" in os.environ: + cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] + + if self.compiler.compiler_type != "msvc": + # Using Ninja-build since it a) is available as a wheel and b) + # multithreads automatically. MSVC would require all variables be + # exported for Ninja to pick it up, which is a little tricky to do. + # Users can override the generator with CMAKE_GENERATOR in CMake + # 3.15+. + if not cmake_generator or cmake_generator == "Ninja": + try: + import ninja + + ninja_executable_path = Path(ninja.BIN_DIR) / "ninja" + cmake_args += [ + "-GNinja", + f"-DCMAKE_MAKE_PROGRAM:FILEPATH={ninja_executable_path}", + ] + except ImportError: + pass + + else: + # Single config generators are handled "normally" + single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) + + # CMake allows an arch-in-generator style for backward compatibility + contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) + + # Specify the arch if using MSVC generator, but only if it doesn't + # contain a backward-compatibility arch spec already in the + # generator name. + if not single_config and not contains_arch: + cmake_args += ["-A", PLAT_TO_CMAKE[self.plat_name]] + + # Multi-config generators have a different way to specify configs + if not single_config: + cmake_args += [ + f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}" + ] + build_args += ["--config", cfg] + + if sys.platform.startswith("darwin"): + # Cross-compile support for macOS - respect ARCHFLAGS if set + archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) + if archs: + cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] + + # Set CMAKE_BUILD_PARALLEL_LEVEL to control the parallel build level + # across all generators. + if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: + # self.parallel is a Python 3 only way to set parallel jobs by hand + # using -j in the build_ext call, not supported by pip or PyPA-build. + if hasattr(self, "parallel") and self.parallel: + # CMake 3.12+ only. + build_args += [f"-j{self.parallel}"] + + build_temp = Path(self.build_temp) / ext.name + if not build_temp.exists(): + build_temp.mkdir(parents=True) + + print([f for f in os.listdir(ext.sourcedir)]) + print("'{}'".format(ext.sourcedir), cmake_args) + subprocess.run( + ["cmake", ext.sourcedir, *cmake_args], cwd=build_temp, check=True + ) + subprocess.run( + ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True + ) + +setup( + name="inkcpp-py", + version="0.1.1", + author="Julian Benda", + author_email="julian.benda@ovgu.de", + description="Python bindings for InkCPP a Inkle runtime written in C++", + long_description="For all issues and ideas please visit the repository at https://github.com/JBenda/inkcpp", + zip_safe=False, + ext_modules=[CMakeExtension("inkcpp_py")], + cmdclass={"build_ext": CMakeBuild}, + python_requires=">=3.7", + py_modules=[], +) diff --git a/shared/public/config.h b/shared/public/config.h index 1f429f0f..23efc89b 100644 --- a/shared/public/config.h +++ b/shared/public/config.h @@ -1,43 +1,45 @@ #pragma once #ifdef INKCPP_API -#define INK_ENABLE_UNREAL +# define INK_ENABLE_UNREAL #else -#define INK_ENABLE_STL -#define INK_ENABLE_CSTD +# define INK_ENABLE_STL +# define INK_ENABLE_CSTD #endif // Only turn on if you have json.hpp and you want to use it with the compiler // #define INK_EXPOSE_JSON -namespace ink::config { - /// set limitations which are required to minimize heap allocations. - /// if required you can set them to -x then, the system will use dynamic - /// allocation for this type, with an initial size of x. - static constexpr int limitGlobalVariables = -50; - static constexpr int limitGlobalVariableObservers = -10; - static constexpr int limitThreadDepth = -10; - static constexpr int limitEvalStackDepth = -20; - static constexpr int limitContainerDepth = -20; - /** number of lists which can be accessed with get_var - * before the story must continue - * @attention list vars are only valid until the story continous! - */ - static constexpr int limitEditableLists = -5; - /// number of simultaneous active tags - static constexpr int limitActiveTags = -10; - // temporary variables and callstack; - static constexpr int limitRuntimeStack = -20; - // references and callstack - static constexpr int limitReferenceStack = -20; - // max number of elements in one output (a string is one element) - // no dynamic support now! (FIXME) - static constexpr int limitOutputSize = 200; - // max number of choices per choice - static constexpr int maxChoices = 10; - // max number of list types, and there total amount of flags - static constexpr int maxListTypes = -20; - static constexpr int maxFlags = -200; - // number of max initelized lists - static constexpr int maxLists = -50; -} +namespace ink::config +{ +/// set limitations which are required to minimize heap allocations. +/// if required you can set them to -x then, the system will use dynamic +/// allocation for this type, with an initial size of x. +static constexpr int limitGlobalVariables = -50; +static constexpr int limitGlobalVariableObservers = -10; +static constexpr int limitThreadDepth = -10; +static constexpr int limitEvalStackDepth = -20; +static constexpr int limitContainerDepth = -20; +/** number of lists which can be accessed with get_var + * before the story must continue + * @attention list vars are only valid until the story continous! + */ +static constexpr int limitEditableLists = -5; +/// number of simultaneous active tags +static constexpr int limitActiveTags = -10; +// temporary variables and callstack; +static constexpr int limitRuntimeStack = -20; +// references and callstack +static constexpr int limitReferenceStack = -20; +// max number of elements in one output (a string is one element) +// no dynamic support now! (FIXME) +static constexpr int limitOutputSize = 200; +// max number of choices per choice +static constexpr int maxChoices = -10; +// max number of list types, and there total amount of flags +static constexpr int maxListTypes = -20; +static constexpr int maxFlags = -200; +// number of max initelized lists +static constexpr int maxLists = -50; +static constexpr int maxArrayCallArity = 10; +} // namespace ink::config