diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b72882c..e6f70ac6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,33 +81,23 @@ jobs: shell: bash run: | if [ "$RUNNER_OS" == "Windows" ]; then - cmake --build ./build --target ALL_BUILD --config Debug + cmake --build ./build --config Debug else - cmake --build ./build --target all --config Debug + cmake --build ./build --config Debug fi - - name: Tests - shell: bash - run: cd build && ctest -C Debug --verbose - - name: Valgrind if: ${{ matrix.os == 'ubuntu-latest' }} shell: bash run: | - valgrind --suppressions=./wren.supp ./build/WrenBind17_tests + sudo apt-get install -y lcov valgrind + valgrind --suppressions=./wren.supp ./build/WrenBind17_Tests - - name: Coverage - if: ${{ matrix.os == 'ubuntu-latest' }} + - name: Tests shell: bash - run: | - lcov --directory ./build --capture --output-file coverage.info; - lcov --remove coverage.info '/usr/*' "${HOME}"'/.cache/*' '*tests*' '*catch2*' '*vm*' --output-file coverage.info; - lcov --list coverage.info; - bash <(curl -s https://codecov.io/bash) -f coverage.info || echo "Codecov did not collect coverage reports"; + run: cd build && ctest -C Debug --verbose - - name: Create Changelog - id: create_changelog - if: startsWith(github.ref, 'refs/tags/v') + - name: Changelog shell: bash run: | last_tag=$(git describe --tags --abbrev=0 @^ || true) @@ -130,3 +120,50 @@ jobs: omitNameDuringUpdate: true prerelease: false token: ${{ secrets.GITHUB_TOKEN }} + + docs: + name: Documentation + needs: [build] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + submodules: true + + - name: Dependencies + shell: bash + run: | + sudo apt-get install doxygen zip unzip -y + wget https://github.com/gohugoio/hugo/releases/download/v0.74.1/hugo_extended_0.74.1_Linux-64bit.tar.gz + tar xvf hugo_extended_0.74.1_Linux-64bit.tar.gz + sudo chmod +x ./hugo + ./hugo version + wget https://github.com/matusnovak/doxybook2/releases/download/v1.1.6/doxybook2-linux-amd64-v1.1.6.zip + unzip doxybook2-linux-amd64-v1.1.6.zip + sudo cp bin/doxybook2 /usr/local/bin/doxybook2 + sudo chmod +x /usr/local/bin/doxybook2 + + - name: Generate documentation + shell: bash + run: | + doxygen + doxybook2 \ + --input temp/xml \ + --output docs/content \ + --config docs/.doxybook/config.json + rm -rf docs/content/Examples + rm -rf docs/content/Pages + + - name: Build static pages + shell: bash + run: | + cd docs + ../hugo + + - name: Deploy + if: startsWith(github.ref, 'refs/tags/v') + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./docs/public diff --git a/.gitmodules b/.gitmodules index bfc1f56c..d7db74ca 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,6 @@ [submodule "libs/Catch2"] path = libs/Catch2 url = https://github.com/catchorg/Catch2.git -[submodule "docs/themes/hugo-theme-learn"] - path = docs/themes/hugo-theme-learn - url = https://github.com/matcornic/hugo-theme-learn +[submodule "docs/themes/hugo-book"] + path = docs/themes/hugo-book + url = https://github.com/alex-shpak/hugo-book diff --git a/CMakeLists.txt b/CMakeLists.txt index b12b7694..0ed86b9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,61 +1,42 @@ cmake_minimum_required(VERSION 3.0) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/modules") + project(WrenBind17) option(WRENBIND17_BUILD_TESTS "Build with tests" OFF) option(WRENBIND17_BUILD_WREN "Build Wren library too" OFF) -if(WRENBIND17_BUILD_TESTS) - # Code Coverage Configuration - add_library(coverage_config INTERFACE) - - if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") - # Add required flags (GCC & LLVM/Clang) - target_compile_options(coverage_config INTERFACE - -O0 # no optimization - -g # generate debug info - --coverage # sets all required flags - ) - if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.13) - target_link_options(coverage_config INTERFACE --coverage) - else() - target_link_libraries(coverage_config INTERFACE --coverage) - endif() - endif() -endif() - +# Add WrenBind17 header only library add_library(${PROJECT_NAME} INTERFACE) set(WRENBIND17_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) -target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(${PROJECT_NAME} INTERFACE ${WRENBIND17_INCLUDE_DIR}) if(WRENBIND17_BUILD_TESTS OR WRENBIND17_BUILD_WREN) - # Wren - set(WREN_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/wren/src/include) - file(GLOB_RECURSE WREN_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/libs/wren/src/vm/*.c) - set_source_files_properties(${WREN_SOURCES} PROPERTIES LANGUAGE C) - add_library(Wren STATIC ${WREN_SOURCES}) - target_include_directories(Wren PRIVATE ${WREN_INCLUDE_DIR}) - target_include_directories(Wren PUBLIC ${WREN_INCLUDE_DIR}) - target_compile_definitions(Wren PRIVATE WREN_OPT_META=0 WREN_OPT_RANDOM=0) + # Find Wren library + find_package(Wren REQUIRED) endif() if(WRENBIND17_BUILD_TESTS) - target_link_libraries(${PROJECT_NAME} INTERFACE coverage_config) + # Find Catch2 library + find_package(Catch2 REQUIRED) + # Add tests enable_testing() - # Catch2 - set(CATCH2_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libs/Catch2/single_include) - - set(TEST_TARGET ${PROJECT_NAME}_tests) file(GLOB TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp) file(GLOB TEST_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.hpp) file(GLOB LIB_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/include/wrenbind17/*.hpp) - add_executable(${TEST_TARGET} ${TEST_SOURCES} ${TEST_HEADERS} ${LIB_HEADERS}) - set_target_properties(${TEST_TARGET} PROPERTIES CXX_STANDARD 17 CXX_EXTENSIONS OFF) - target_include_directories(${TEST_TARGET} PRIVATE ${CATCH2_INCLUDE_DIR}) - target_link_libraries(${TEST_TARGET} PUBLIC Wren ${PROJECT_NAME}) - target_link_libraries(${TEST_TARGET} PRIVATE coverage_config) + add_executable(${PROJECT_NAME}_Tests ${TEST_SOURCES} ${TEST_HEADERS} ${LIB_HEADERS}) + set_target_properties(${PROJECT_NAME}_Tests PROPERTIES CXX_STANDARD 17 CXX_EXTENSIONS OFF) + target_include_directories(${PROJECT_NAME}_Tests PRIVATE ${CATCH2_INCLUDE_DIR}) + target_link_libraries(${PROJECT_NAME}_Tests PUBLIC Wren ${PROJECT_NAME}) + if(UNIX AND NOT APPLE) + # Coverage info + target_compile_options(${PROJECT_NAME}_Tests PRIVATE --coverage -g -O0) + target_link_options(${PROJECT_NAME}_Tests PRIVATE --coverage) + endif() if(MINGW) - target_compile_options(${TEST_TARGET} PRIVATE -Wa,-mbig-obj) + target_compile_options(${PROJECT_NAME}_Tests PRIVATE -Wa,-mbig-obj) endif() - add_test(NAME ${TEST_TARGET} COMMAND ${TEST_TARGET}) + add_test(NAME ${PROJECT_NAME}_Tests COMMAND ${PROJECT_NAME}_Tests) endif() + diff --git a/LICENSE b/LICENSE index ef10f6e1..68860cc7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ -MIT License - -Copyright (c) 2019 Matus Novak - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +MIT License + +Copyright (c) 2019-2020 Matus Novak + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index d827e034..cbd5d225 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,40 @@ # WrenBind17 -[![Build Status](https://travis-ci.com/matusnovak/wrenbind17.svg?branch=master)](https://travis-ci.com/matusnovak/wrenbind17) [![Build status](https://ci.appveyor.com/api/projects/status/fy974aj37cdyxc0i/branch/master?svg=true)](https://ci.appveyor.com/project/matusnovak/wrenbind17/branch/master) [![CircleCI](https://circleci.com/gh/matusnovak/wrenbind17.svg?style=svg)](https://circleci.com/gh/matusnovak/wrenbind17) [![codecov](https://codecov.io/gh/matusnovak/wrenbind17/branch/master/graph/badge.svg)](https://codecov.io/gh/matusnovak/wrenbind17) +[![build](https://github.com/matusnovak/wrenbind17/workflows/build/badge.svg?branch=master)](https://github.com/matusnovak/wrenbind17/actions) -WrenBind17 is a C++17 wrapper for [Wren programming language](http://wren.io/). This project was heavily inspired by [pybind11](https://github.com/pybind/pybind11) and by [Wren++](https://github.com/Nelarius/wrenpp). This library is header only and does not need any compilation steps. Simply include the `` header in your application and you are good to go! +WrenBind17 is a C++17 wrapper for [Wren programming language](http://wren.io/). This project was heavily inspired by [pybind11](https://github.com/pybind/pybind11) and by [Wren++](https://github.com/Nelarius/wrenpp). This library is header only and does not need any compilation steps. Simply include the WrenBind17 header ``, link the Wren library, and you are good to go. ## Features * Header only. * Works with Visual Studio 2017, MinGW-w64, Linux GCC, and Apple Clang on Mac OSX. * C++17 so you don't need to use `decltype()` on class methods to bind them to Wren. -* [Foreign modules are automatically generated for you](https://matusnovak.github.io/wrenbind17/tutorial/hello_world.html). You don't need to write the extra foreign classes in separate file. +* Foreign modules are automatically generated for you. You don't need to write the extra foreign classes in separate file. * **Supports strict type safety.** You won't be able to pass just any variable from Wren back to the C++, preventing you getting segmentation faults. -* Objects are wrapped in `std::shared_ptr` so you have easier access when passing objects around. +* **Objects are wrapped in std::shared_ptr so you have easier access when passing objects around.** This also enables easy object lifetime management. * Easy binding system inspired by [pybind11](https://github.com/pybind/pybind11). -* [Works with exceptions](https://matusnovak.github.io/wrenbind17/tutorial/exceptions.html). -* [Upcasting to base types when passing C++ instances](https://matusnovak.github.io/wrenbind17/tutorial/upcasting.html). +* Works with exceptions. +* Upcasting to base types when passing C++ instances. * Memory leak tested. -* Supports `std::variant`. -* Supports [std::map std::unordered_map](https://matusnovak.github.io/wrenbind17/tutorial/maps/) and [std::vector std::list](https://matusnovak.github.io/wrenbind17/tutorial/lists/) via helper classes (optional). -* [Easy binding of operators](https://matusnovak.github.io/wrenbind17/tutorial/overload-operators.html) such as `+`, `-`, `[]`, etc. -* [Long but easy to follow tutorial](https://matusnovak.github.io/wrenbind17/tutorial/installation.html). -* [Supports Fn.new{}](https://matusnovak.github.io/wrenbind17/tutorial/callbacks.html). -* [Supports inheritance (a workaround)](https://matusnovak.github.io/wrenbind17/tutorial/inheritance.html). -* [Supports modularity via look-up paths](https://matusnovak.github.io/wrenbind17/tutorial/modules.html). -* [Supports passing variables by move](https://matusnovak.github.io/wrenbind17/tutorial/call-wren.html) +* Supports STL containers such as `std::variant`, `std::optional`, `std::vector`, `std::list`, `std::deque`, `std::set`, `std::unordered_set`, `std::map`, `std::unordered_map` which can be handled either natively or as a foreign class. +* Easy binding of operators such as `+`, `-`, `[]`, etc. +* Long but easy to follow tutorial ([here](https://matusnovak.github.io/wrenbind17/tutorial/)). +* Supports native lists and native maps. +* Supports Fn.new{}. +* Supports inheritance (a workaround). +* Supports modularity via look-up paths. +* Supports passing variables by move. ## Limitations * Passing by a reference to a Wren function will create a copy. Use a pointer if you do not wish to create copies and maintain single instance of a given class. This does not affect C++ member functions that return a reference, in that case it will be treated exactly same as a pointer. +* STL containers `std::unique_ptr`, `std::queue`, `std::stack` are not supported. -## Not yet implemented - -* Lambdas (very tricky due to passing function pointers, most likely not ever be implemented). -* `..`, `...`, and `is` operator binding. -* Helper classes for binding `std::queue`, `std::deque`, `std::stack`, `std::set`, and `std::unordered_set`. +## TODO +* Lambdas +* `..`, `...`, and `is` operators +* lcov coverage (currently broken with gcc-9) ## Example @@ -127,9 +127,7 @@ int main(int argc, char *argv[]) { ## Documentation -Tutorials can be found [**here**](https://matusnovak.github.io/wrenbind17/tutorial/installation/) - -And the autogenerated API documentation via Doxygen [**here**](https://matusnovak.github.io/wrenbind17/modules/group__wrenbind17/) +Tutorials and API documentation can be found here: [https://matusnovak.github.io/wrenbind17/](https://matusnovak.github.io/wrenbind17/) ## Build @@ -148,7 +146,7 @@ Pull requests are welcome ``` MIT License -Copyright (c) 2019 Matus Novak +Copyright (c) 2019-2020 Matus Novak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/.doxybook/config.json b/docs/.doxybook/config.json index 68dda9f5..1ae6babb 100644 --- a/docs/.doxybook/config.json +++ b/docs/.doxybook/config.json @@ -8,5 +8,7 @@ "indexGroupsName": "_index", "indexNamespacesName": "_index", "indexRelatedPagesName": "_index", - "indexExamplesName": "_index" -} \ No newline at end of file + "indexExamplesName": "_index", + "mainPageInRoot": true, + "mainPageName": "_index" +} diff --git a/docs/.doxybook/templates/header.tmpl b/docs/.doxybook/templates/header.tmpl deleted file mode 100644 index 527f5cea..00000000 --- a/docs/.doxybook/templates/header.tmpl +++ /dev/null @@ -1,7 +0,0 @@ ---- -{% if exists("title") %}title: {{title}}{% else if exists("name") %}title: {{name}}{% endif %} -{% if exists("summary") %}summary: {{summary}} {% endif%} -{% include "meta" %} ---- - -{% if exists("kind") %}{% if kind != "page" %}**{{name}} {{title(kind)}} Reference**{% endif %}{% endif %} diff --git a/docs/config.toml b/docs/config.toml index cbb9a772..2483c1c8 100644 --- a/docs/config.toml +++ b/docs/config.toml @@ -2,11 +2,12 @@ baseURL = "https://matusnovak.github.io/wrenbind17" editURL = "https://github.com/matusnovak/wrenbind17/tree/master/docs/content" languageCode = "en-us" title = "WrenBind17" -theme = "hugo-theme-learn" -pygmentscodefences = true +theme = "hugo-book" +pygmentsCodeFences = true -[[Languages.en.menu.shortcuts]] - name = " GitHub repo" - identifier = "ds" - url = "https://github.com/matusnovak/wrenbind17/" - weight = 10 +[params] + BookEditPath = 'tree/master/docs/content' + BookSearch = true + BookRepo = 'https://github.com/matusnovak/wrenbind17' + BookToC = true + BookMenuBundle = '/menu' diff --git a/docs/content/Tutorial/_index.md b/docs/content/Tutorial/_index.md index ca4e7f47..a0833f60 100644 --- a/docs/content/Tutorial/_index.md +++ b/docs/content/Tutorial/_index.md @@ -2,4 +2,16 @@ title: Tutorial --- -{{% children %}} +# Tutorial + + * [1. Installation]({{< relref "install.md" >}}) + * [2. Hello World]({{< relref "hello_world.md" >}}) + * [3. Call Wren function]({{< relref "call_wren.md" >}}) + * [4. Supported types]({{< ref "types.md" >}}) + * [5. Executing from file]({{< ref "execute_code.md" >}}) + * [6. Custom types]({{< ref "custom_types.md" >}}) + * [7. Class operators]({{< ref "operators.md" >}}) + * [8. Modules and files]({{< ref "modules.md" >}}) + * [9. Customize VM]({{< ref "customize.md" >}}) + * [10. Fn.new and callbacks]({{< ref "fn.md" >}}) + * [11. STL containers]({{< ref "stl.md" >}}) diff --git a/docs/content/Tutorial/abstract-classes.md b/docs/content/Tutorial/abstract-classes.md deleted file mode 100644 index 7cfc2f33..00000000 --- a/docs/content/Tutorial/abstract-classes.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: Abstract classes -weight: 70 ---- - -What if you want to pass an abstract class to Wren? You can't allocate it, but you can only pass it around as a reference or a pointer? Imagine a specific derived "Entity" class that has a common abstract/interface class? - -The only thing you have to do is to NOT to add constructor by calling `ctor`. - -```cpp -wren::VM vm; -auto& m = vm.module("mymodule"); - -// Add class "Vec3" -auto& cls = m.klass("Entity"); -// cls.ctor<>(); Do not add constructor! -cls.func<&Entity::foo>("foo"); -``` - diff --git a/docs/content/Tutorial/bind-cpp-class.md b/docs/content/Tutorial/bind-cpp-class.md deleted file mode 100644 index 8347524d..00000000 --- a/docs/content/Tutorial/bind-cpp-class.md +++ /dev/null @@ -1,350 +0,0 @@ ---- -title: Bind C++ class -weight: 60 ---- - -To bind a C++ class, you will first need to create a new module. Creating a new module is done per VM instance. If you have multiple VMs in your application, they won't share the same modules. You would have to create the module for each of your VM instances. Copying modules between instances is not possible. - -```cpp -wren::VM vm; - -// Create module called "mymodule" -auto& m = vm.module("mymodule"); -``` - -You can create as many modules as you want. Additionally, calling the method `module(...)` multiple times with the same name won't create duplicates. For example: - -```cpp -wren::VM vm; - -auto& m0 = vm.module("mymodule"); -auto& m1 = vm.module("mymodule"); - -// m0 and m1 now point to the exact same module -``` - -{{% notice info %}} -Modules must be used via a reference `auto& m = ...`. Copying modules is not allowed and causes compilation error. -{{% /notice %}} - -### Class member functions - -Classes are added into the modules in the following way: - -```cpp -class Foo { -public: - Foo(const std::string& msg) { - ... - } - - void bar() { - - } - - int baz() const { - - } -}; - -wren::VM vm; -auto& m = vm.module("mymodule"); - -// Add class "Foo" -auto& cls = m.klass("Foo"); - -// Define constructor (you can only specify one constructor) -cls.ctor(); - -// Add some methods -cls.func<&Foo::bar>("bar"); -cls.func<&Foo::baz>("baz"); -``` - -The class functions (methods) are added as a template argument, not as the function argument. This is due to the how Wren is built. Because of this implementation, you will also get extra performance, because the pointers to the class functions are optimized at compile time, there are no lookup maps. - -Now inside of your Wren script, you can do the following: - -```js -import "mymodule" for Foo - -var foo = Foo.new("Message") -foo.bar(); -var i = foo.baz(); -``` - -Please note that you don't have to manually create file "mymodule.wren" and add all of your C++ foreign classes into it manually. Everything is automatically generated by the `wren::VM`. You can get the "raw" contents of the module that will be put into Wren by simply calling `.str()` on the module (e.g. `vm.module("mymodule").str();`). This will print the contents of that module as a Wren code with foreign classes. - -### Handling function pointer ambiguity - -In case you have multiple functions with the same name, you will have to use `static_cast` to explicitly tell the compiler which function you want. For example: - -```cpp -class Foo { - const std::string& getMsg() const; - std::string& getMsg(); -}; - -wren::VM vm; -auto& m = vm.module("mymodule"); -auto& cls = vm.klass("Foo"); -cls.func(&Foo::getMsg)>("getMsg"); -``` - -### Static functions - -To add a static function, simply call the `funcStatic` instead of `func` as shown below: - -```cpp -class Log { - static void debug(const std::string& text); - static void error(const std::string& text); - static void info(const std::string& text); -}; - -wren::VM vm; -auto& m = vm.module("mymodule"); -auto& cls = m.klass("Log"); -cls.funcStatic<&Log::debug>("debug"); -cls.funcStatic<&Log::error>("error"); -cls.funcStatic<&Log::info>("info"); -``` - -### Via external functions - - -Suppose you have some C++ class you want to bind to Wren, but you can't modify this class because it is from some other library, for example from STL. Or, you want to add some custom behavior but such C++ method does not exist. In this case, you can do the following: - -```cpp -// Custom member function with "self" as this pointer -template -bool vectorContains(std::vector& self, const T& value) { - return std::find(self.begin(), self.end(), value) != self.end(); -} - -// Custom static function without any "self" -template -bool vectorFactory(const T& value) { - std::vector vec; - vec.push_back(value); - return vec; -} - -auto& cls = m.klass>("VectorInt"); -cls.ctor<>(); -cls.funcExt<&vectorContains>("contains"); -cls.funcStaticExt<&vectorFactory>("factory"); -``` - -```js -import "mymodule" for VectorInt - -var a = VectorInt.new() -var b = VectorInt.factory(456) // Calls vectorFactory -a.contains(123) // returns bool -``` - -"Ext" simply means that this is an external function, and the first parameter **must** accept a reference to the class you are binding. However, static functions do not require this. Do not mistake this with "extern" C++ keyword. It has nothing to do with that. (Maybe there is a better word for it?) Additionally, if you look at the `vectorContains` function from above, there is no "this" pointer because this is not a member function. Instead, the "this" is provided as a custom parameter in the first position. This also works with `propExt` and `propReadonlyExt`, and static via `funcStaticExt`. - -## Binding C++ class varialbles - -There are two ways how to add C++ class variables to Wren. One is [as variables](#as-variables) and the other way is [as properties](#as-properties). In the end, they will act exactly same in Wren. The only difference is on the C++ side! - -### As variables - -One way is to have a field and simply bind it to the Wren: - -```cpp -struct Vec3 { - float x = 0; - float y = 0; - float z = 0; -}; - -wren::VM vm; -auto& m = vm.module("mymodule"); - -// Add class "Vec3" -auto& cls = m.klass("Vec3"); -cls.ctor<>(); -cls.var<&Vec3::x>("x"); -cls.var<&Vec3::y>("y"); -cls.var<&Vec3::z>("z"); -``` - -### As properties - -Another way is to have a getter and a setter and bind those to the Wren: - -```cpp -class Vec3 { -public: - float getX() const { return x; } - void setX(float value) { x = value; } - float getY() const { return y; } - void setY(float value) { y = value; } - float getZ() const { return z; } - void setZ(float value) { z = value; } -private: - float x = 0; - float y = 0; - float z = 0; -}; - -wren::VM vm; -auto& m = vm.module("mymodule"); - -// Add class "Vec3" -auto& cls = m.klass("Vec3"); -cls.ctor<>(); -cls.prop<&Vec3::getX, &Vec3::setX>("x"); -cls.prop<&Vec3::getY, &Vec3::setY>("y"); -cls.prop<&Vec3::getZ, &Vec3::setZ>("z"); -``` - -### Result - -Equivalent wren code for both using `.var<&field>("name")` or `.prop<&getter, &setter>("name")`: - -```js -// Autogenerated -foreign class Vec3 { - construct new () {} - - foreign x - foreign x=(rhs) - - foreign y - foreign y=(rhs) - - foreign z - foreign z=(rhs) -} -``` - -And then simply use it in Wren as: - -```js -import "mymodule" for Vec3 - -var v = Vec3.new() -v.x = 1.23 -v.y = 0.0 -v.z = 42.42 -``` - -### Read only variables - -To bind read-only variables you can use `varReadonly` function. This won't define a Wren setter and therefore the variable can be only read. - -```cpp -class Vec3 { -public: - Vec3(float x, float y, float z) {...} - - const float x; - const float y; - const float z; -}; - -wren::VM vm; -auto& m = vm.module("mymodule"); - -// Add class "Vec3" -auto& cls = m.klass("Vec3"); -cls.ctor<>(); -cls.varReadonly<&Vec3::x>("x"); -cls.varReadonly<&Vec3::y>("y"); -cls.varReadonly<&Vec3::z>("z"); -``` - -Equivalent wren code: - -```js -// Autogenerated -foreign class Vec3 { - construct new () {} - - foreign x - - foreign y - - foreign z -} -``` - -And then simply use it in Wren as: - -```js -import "mymodule" for Vec3 - -var v = Vec3.new(1.1, 2.3, 3.3) -System.print("X value is: %(v.x)") // ok -v.x = 1.23 // error -``` - -For read-only properties, you can use `propReadonly` as shown below: - -```cpp -// Add class "Vec3" -auto& cls = m.klass("Vec3"); -cls.ctor<>(); -cls.propReadonly<&Vec3::getX>("x"); -cls.propReadonly<&Vec3::getY>("y"); -cls.propReadonly<&Vec3::getZ>("z"); -``` - -### Variables via external functions - -Sometimes the property simply does not exist in the C++ class you want to use. So, you somehow need to add this into Wren without changing the original class code. One way to do it is through "external" functions. This is simply a function that is static and **must** accept the first parameter as a reference to the class instance. - -```cpp -static float getVec3X(Vec3& self) { - return self.x; -} - -static float getVec3Y(Vec3& self) { ... } -static float getVec3Z(Vec3& self) { ... } - -// Add class "Vec3" -auto& cls = m.klass("Vec3"); -cls.ctor<>(); -cls.propExtReadonly<&getVec3X>("x"); -cls.propExtReadonly<&getVec3Y>("y"); -cls.propExtReadonly<&getVec3Z>("z"); -``` - -### Static variables - -Static variables in Wren are not supported. However, you could cheat a bit with the following code: - -```cpp -class ApplicationGlobals { -public: - std::string APP_NAME = "Lorem Ipsum Donor"; -}; - -int main() { - ... - - wren::VM vm(...); - auto& m = vm.module("mymodule"); - - auto& cls = m.klass("ApplicationGlobals"); - cls.var<&ApplicationGlobals::APP_NAME>("APP_NAME"); - m.append("var Globals = ApplicationGlobals.new()\n"); - - return 0; -} -``` - -Wren code: - - -```js -import "mymodule" for Globals - -print("Name: %(Globals.APP_NAME)") -``` - -What does `m.append` do? It allows you to add arbitraty Wren code into the auto generated Wren code from your C++ classes. Anything you will put into the append function will appear at the bottom of the autogenerated code. Calling the append function multiple times is allowed, it will **not** override previous append call. In this case above, the ApplicationGlobals is created as an instance named Globals. So, from the user's perspective in the Wren code, it appears as a static member variable. The name **must start with a capital letter**, otherwise Wren will not allow you to import that variable. diff --git a/docs/content/Tutorial/call-cpp.md b/docs/content/Tutorial/call-cpp.md deleted file mode 100644 index 7246bb61..00000000 --- a/docs/content/Tutorial/call-cpp.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Call C++ and pass variables -weight: 100 ---- - -Calling C++ class methods from Wren is easy. (See [Bind C++ Class]({{< ref "bind-cpp-class.md" >}}) tutorial). But, what if you want to return a C++ class instance from a C++ member function, or the other way around? - -## Return class instance from C++ - -Consider the following example: - -```cpp -class Foo { -public: - Foo() = default; - - Foo getAsCopy(); - Foo* getAsPointer(); - Foo& getAsReference(); - const Foo& getAsConstReference(); - std::shared_ptr getAsShared(); -}; - -int main() { - ... - wren::VM vm(...); - auto& m = vm.module("mymodule"); - - auto& cls = m.klass("Foo"); - cls.func<&Foo::getAsCopy>("getAsCopy"); - cls.func<&Foo::getAsPointer>("getAsPointer"); - cls.func<&Foo::getAsReference>("getAsReference"); - cls.func<&Foo::getAsConstReference>("getAsConstReference"); - cls.func<&Foo::getAsShared>("getAsShared"); -} -``` - -**All of these methods are valid, and can be used by the Wren. The problem is with the lifetime of the instance.** - -### Return by copy - -Returning by a copy is allowed as long as the class supports copying. If your class is not copyable, binding `getAsCopy` will cause compilation error. The new class copy will be put into a `std::shared_ptr` and will be deleted once Wren's garbage collector removes it. - -### Return by a pointer or a reference - -This is always allowed, no matter the class. Returning as a pointer or as a reference has exactly the same effect. The class instance will be put into a `std::shared_ptr` with no destructor, meaning the instance will **not** be deleted once the Wren's garbage collector removes it. The lifetime of the instance is determined by the C++. - -### Return by a shared pointer - -This is the most safe way how to pass around class instances. The lifetime of the object is determined by the shared pointer itself. You and Wren will both extend and handle the lifetime. The class will get deleted after Wren's garbage collector removes it **and** your shared pointer on C++ side will be cleared/reset/deleted. - -## Pass class instance to C++ - -Consider the following example: - -```cpp -class Foo { -public: - Foo() = default; - - setAsCopy(Foo foo); - setAsPointer(Foo* foo); - setAsReference(Foo& foo); - setAsConstReference(const Foo& foo); - setAsShared(const std::shared_ptr& foo); -}; - -int main() { - ... - wren::VM vm(...); - auto& m = vm.module("mymodule"); - - auto& cls = m.klass("Foo"); - cls.func<&Foo::setAsCopy>("setAsCopy"); - cls.func<&Foo::setAsPointer>("setAsPointer"); - cls.func<&Foo::setAsReference>("setAsReference"); - cls.func<&Foo::setAsConstReference>("setAsConstReference"); - cls.func<&Foo::setAsShared>("setAsShared"); -} -``` - -**All of these methods are valid.** - -### Pass by a copy - -Passing by copy is self-explanatory. This is only allowed if the class is copyable. Otherwise binding this function will cause compilation error. - -### Pass by a reference or a pointer - -This is not recommended unless you know exactly what you are doing. The lifetime of the class instance you are passing is determined how the class instance was created in the first place. If the instance was created on Wren, you have no idea when the instance will get deleted, and you have no way of detecting that. **This is OK only if you are only using that pointer/reference locally in the function body**, because the garbage collector will not get executed while Wren is waiting for the function to return. If you are storing the pointer/reference globally or on a class level, you might have problems depending on the instance lifetime. - -### Pass by a shared pointer - -This is the most safe method. You will essentially extend the lifetime of the instance. All rules of the shared pointer apply here. diff --git a/docs/content/Tutorial/call-wren.md b/docs/content/Tutorial/call-wren.md deleted file mode 100644 index 2633cf71..00000000 --- a/docs/content/Tutorial/call-wren.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -title: Call Wren and pass variables -weight: 90 ---- - -## Basics - -In order to call Wren functions, you will need to define those functions in a Wren class. The most common way to do that is to declare the function as a static function: - -```js -class Main { - static main(a, b) { - return a + b - } -} -``` - -To find the function, you will first need to compile the source and look for the specific class by its name. When you get the class, you can find the any specific member function inside of that class. To execute the function, call the `operator()(Args&&... args)` method on the `wren::Method`. - -```cpp -wren::VM vm; - -// Runs the code as a specific "somemodule" module. -vm.runFromSource("somemodule", "...code from above..."); - -// Find the class -wren::Variable wrenClass = vm.find("somemodule", "Main"); - -// Find the function with two arguments -wren::Method wrenMethod = wrenClass.func("main(_, _)"); - -// Call the function with two integers -wren::Any result = wrenMethod(10, 15); - -// Check if the result is some specific C++ type -result.is(); // true - -// Cast the result to C++ type -int sum = result.as(); // 15 -``` - -{{% notice info %}} -The lifetime of the object returned by calling Wren methods is determined by the `wren::Any` class. If you return an instance of a class from Wren to C++, and you capture that result as a pointer/reference to the class (i.e. `result.as()` or `result.as()`) then the lifetime of that pointer or reference will depend on the lifetime of `wren::Any`. If you are returning a C++ class created on Wren from Wren into C++ side, and you want to keep the instance for longer time, use `result.shared()` which will return `std::shared_ptr`. This works because **all C++ classes created on Wren are constructed as a shared pointer**. -{{% /notice %}} - -## Wren functions don't need to be static - -```js -class Main { - construct new() { - - } - - main(a, b) { - return a + b - } -} - -var Instance = Main.new() -``` - -And then in C++: - -```cpp -// Find the class -wren::Variable wrenClass = vm.find("somemodule", "Instance"); - -// Find the function with two arguments -wren::Method wrenMethod = wrenClass.func("main(_, _)"); -``` - -## Pass class instance to Wren by copy/reference - -This is the default behavior of passing class instances to Wren. As shown below, the class will be moved as a copy. Your class must be trivially copyable or a copy constructor defined! Because passing anything into a function call will implicitly the use a reference, passing explicitly by a reference also creates a copy. Use a pointer if you do not want to create a copy. - -```cpp -Foo foo = Foo("Hello World"); -wren::Method main = vm.find("main", "Main").func("main(_)"); -main(foo); -``` - -## Pass class instance to Wren by move - -It is possible to pass it to Wren by move as shown below. The instance will be moved into a new `std::shared_ptr` handled by Wren. This is possible only if your class has a move constructor! - -```cpp -Foo foo = Foo("Hello World"); -wren::Method main = vm.find("main", "Main").func("main(_)"); -main(std::move(foo)); -``` - -## Pass class instance to Wren by pointer - -The instance will be moved into a new `std::shared_ptr` handled by Wren **but the destructor is empty**. Meaning, you will get the instance inside of the Wren, but the lifetime of the instance is determinted solely by the C++. **Make sure the instance you pass as a raw pointer lives longer than the Wren VM**. - -```cpp -Foo foo = Foo("Hello World"); -wren::Method main = vm.find("main", "Main").func("main(_)"); -main(std::move(foo)); -``` - -## Pass class instance to Wren by shared pointer - -This is the recommended method. You will not have any issues with the lifetime of the instance. You will also avoid accidental segmentation faults. Moving shared pointer has no different effect than passing it as a reference or a copy. - -```cpp -std::shared_ptr foo(new Foo("Hello World")); -wren::Method main = vm.find("main", "Main").func("main(_)"); -main(foo); -``` - -## Return class instance from Wren - -Beware that returning C++ class instances from Wren contains same danger as returning instances from any other C++ function. Returning by a copy is safe, returning by a pointer or a reference is very dangerous (but, there is an exception), and returning by a shared pointer is the safest way to do it. - -```cpp -wren::Method method = klass.func("main()"); -wren::Any result = method(); - -Foo result.as(); // As a copy - -Foo& result.as(); // As a reference -Foo* result.as(); // As a pointer - -std::shared_ptr result.shared(); // As a shared pointer -``` - -The Wren language is garbage collected. Therefore once the variable is out of the scope, it may or may not be destroyed. It depends on the implementation of the language. But! **The wren::Any will extend the lifetime of the object** so you can use the pointer as long as the result is alive. Once the wren::Any falls out of the scope, the pointer or a reference may be invalid because the instance may have been garbage collected. diff --git a/docs/content/Tutorial/call_wren.md b/docs/content/Tutorial/call_wren.md new file mode 100644 index 00000000..b8e2d5f8 --- /dev/null +++ b/docs/content/Tutorial/call_wren.md @@ -0,0 +1,280 @@ +--- +title: 3. Call Wren function +--- + +# 3. Call Wren function + +## 3.1. Simple call + +To call a Wren function, you will have to first run the source code. Only then you can use `wren::VM::find` function to find the class defined in the Wren code. This will give you an object of `wren::Variable` which can be any Wren variable from the VM. In this case, it's a class. Next, find the method you want to call via `wren::Variable::func`. + +You will have to specify the exact signature of the method. In this case, the `static main()` has no parameters, therefore the signature is `main()`. If you have a method with multiple parameters, for example `static main(a, b) { ... }`, then the signature is the following: `main(_,_)`. Use underscores to specify parameters (don't use the parameter name). Whitespace is not allowed. + +{{< hint warning >}} +**Warning** + +The `wren::VM::find` throws an exception if the class (or a class instance) is not found in the module. +{{< /hint >}} + +```cpp +int main(int argc, char *argv[]) { + const std::string code = R"( + class Main { + static main() { + System.print("Hello World!") + } + } + )"; + + // Create new VM + wren::VM vm; + + // Runs the code from the std::string as a "main" module + // The System.print("Hello World!") will not be called + // because it is in a function + vm.runFromSource("main", code); + + // Find class Main in module main + wren::Variable mainClass = vm.find("main", "Main"); + + // Find function main() in class Main + wren::Method main = mainClass.func("main()"); + + // Execute the function + // Puts "Hello World!" into the stdout of your terminal. + // This calls the wren::Method::operator()(...) function. + main(); + + return 0; +} +``` + +## 3.2. Call with arguments + +To call a Wren function that accepts arguments, simply look up the function using a signature that also matches the number of parameters of that function. + +See [4. Supported types]({{< ref "types.md" >}}) to see what types are supported when passing arguments into a Wren function. + +{{< hint warning >}} +**Warning** + +If you call a Wren function via the `wren::Method::operator()(...)` C++ function, and the number of arguments do not match the signature, it will throw an exception. +{{< /hint >}} + +```cpp +int main(int argc, char *argv[]) { + const std::string code = R"( + class Main { + static main(a, b) { + x = a + b + System.print("The result is: %(x) ") + } + } + )"; + + // Create new VM + wren::VM vm; + + // Runs the code from the std::string as a "main" module + // The System.print("Hello World!") will not be called + // because it is in a function + vm.runFromSource("main", code); + + // Find class Main in module main + wren::Variable mainClass = vm.find("main", "Main"); + + // Find function main() in class Main + wren::Method main = mainClass.func("main(_,_)"); + + // Execute the function + // Puts "The result is 25" into the stdout of your terminal. + // This calls the wren::Method::operator()(...) function. + main(10, 15); + + return 0; +} +``` + +## 3.3. Return values + +Returning values is simply done via `wren::Any` which can hold any value that Wren can handle. The return value that is held by the `wren::Any` is not guaranteed to be a useable C++ variable. You can call the `wren::Any::is()` function to check if the return value is some C++ type. Due to the design of Wren, the code below will cast the two integers `10` and `15` into doubles (float64) type, because that's how Wren handles numbers (no real integers). + +The `result.is()` below will work because all doubles can be casted into integers, also the other way around. You will loose precision, if you return a double and get it as an integer. **Only numeric types are casted between each other.** + +You can always extract the value from `wren::Any` as long as `wren::VM` is alive. Calling the `.as()` function multiple times is allowed. + +See [4. Supported types]({{< ref "types.md" >}}) to see what types are supported when returning values from a Wren function. + +```cpp +int main(int argc, char *argv[]) { + const std::string code = R"( + class Main { + static main(a, b) { + return a + b + } + } + )"; + + // Create new VM + wren::VM vm; + + // Runs the code from the std::string as a "main" module + // The System.print("Hello World!") will not be called + // because it is in a function + vm.runFromSource("main", code); + + // Find class Main in module main + wren::Variable mainClass = vm.find("main", "Main"); + + // Find function main() in class Main + wren::Method main = mainClass.func("main(_,_)"); + + // Execute the function + // This calls the wren::Method::operator()(...) function. + wren::Any result = main(10, 15); + + assert(result.is()); + std::cout << "The result is: " << result.as() << std::endl; + + return 0; +} +``` + +## 3.4. Lifetime of return values + +The lifetime of the returned value is exteded by the `wren::Any`. You can extend the lifetime of the `wren::Any` beyond the lifetime of `wren::VM`. + +Due to the fact that the `wren::Handle` (used by `wren::Any`) is using a weak pointer to the VM, the `wren::Any` becomes empty once the `wren::VM` is destroyed. You can't use it afterwards and any action will result in an exception of type `wren::RuntimeException`. This ensures that if you store `wren::Any` and can't handle the lifetime (for example in a lambda capture) then you won't get segmentation faults. It is handled for you automatically. + +## 3.5. Call non-static methods + +You can call non-static methods in classes. In all of the examples above, the class has a static main function, but this is not needed. You can do the following: + +```js +class Main { + construct new() { + + } + + main(a, b) { + return a + b + } +} + +var Instance = Main.new() +``` + +```cpp +// Find the class +wren::Variable wrenClass = vm.find("somemodule", "Instance"); + +// Find the function with two arguments +wren::Method wrenMethod = wrenClass.func("main(_, _)"); +``` + +{{< hint warning >}} +**Note** + +This only works if the instance variable name starts with a capital letter -> `Instance`. +{{< /hint >}} + +## 3.6. Call with custom types + +You can call Wren function and pass custom C++ types into it. This only works if you have added (binding) your custom type into your Wren VM that you are calling the function in. See [4. Supported types]({{< ref "types.md" >}}) to understand how that is done. + + +### 3.6.1. Pass by a copy/reference + +This is the default behavior of passing class instances to Wren. As shown below, the class will be moved as a copy. Your class must be trivially copyable or a copy constructor defined. Passing anything into a function call will implicitly the use a reference, passing explicitly by a reference also creates a copy. Use a pointer if you do not want to create a copy. Or even better, use a shared pointer. + +```cpp +Foo foo = Foo("Hello World"); +wren::Method main = vm.find("main", "Main").func("main(_)"); +main(foo); +``` + +### 3.6.2. Pass by a move + +It is possible to pass it to Wren by a move as shown below. The instance will be moved into a new `std::shared_ptr` handled by the Wren VM. This is possible only if your class has a move constructor. + +```cpp +Foo foo = Foo("Hello World"); +wren::Method main = vm.find("main", "Main").func("main(_)"); +main(std::move(foo)); +``` + +### 3.6.3. Pass by a pointer + +The instance will be moved into a new `std::shared_ptr` handled by Wren **but the deleter of that shared pointer is empty**. Meaning, you will get the instance inside of Wren, but the lifetime of the instance is determinted solely by the C++. **Make sure the instance you pass as a raw pointer lives longer than the Wren VM**. + +```cpp +Foo foo = Foo("Hello World"); +wren::Method main = vm.find("main", "Main").func("main(_)"); +main(std::move(foo)); +``` + +### 3.6.4. Pass as a shared pointer + +**This is the recommended method.** You will not have any issues with the lifetime of the instance. You will also avoid accidental segmentation faults. Moving shared pointer has no different effect than passing shared pointer as a reference or a copy. It will act exactly the same. Doing this will also extend the lifetime of the `Foo` object held by the shared pointer. + +Deletion happens only when Wren garbace collects the instance and the same shared pointer is also no longer needed on C++ side. + +TL;DR: Nothing special, it is just a shared pointer. + +```cpp +std::shared_ptr foo(new Foo("Hello World")); +wren::Method main = vm.find("main", "Main").func("main(_)"); +main(foo); +``` + +## 3.7. Return custom types from Wren + +Beware that returning C++ class instances from Wren contains same danger as returning instances from any other C++ function. Returning by a copy is safe, returning by a pointer or a reference is very dangerous, and finally returning by a shared pointer is the safest way to do it. + +```cpp +wren::Method method = klass.func("main()"); +wren::Any result = method(); + +Foo result.as(); // As a copy + +Foo& result.as(); // As a reference +Foo* result.as(); // As a pointer + +std::shared_ptr result.shared(); // As a shared pointer +``` + +The Wren language is garbage collected. Therefore once the variable is out of the scope, it may or may not be destroyed. It depends on the implementation of the language. But! **The wren::Any will extend the lifetime of the object**, so you can use the "pointer" (or a reference that `.as()` returns) as long as the result is alive. Once the `wren::Any` falls out of the scope, the pointer or a reference may be invalid because the instance may have been garbage collected. + +## 3.8. Catch Wren errors + +```cpp +#include +namespace wren = wrenbind17; // Alias + +int main(...) { + wren::VM vm; + ... + + try { + wren::Method wrenMain = vm.find("main", "Main").func("main()"); + wrenMain(); + } catch (wren::NotFound& e) { + // Thows only if class "Main" has not been found + std::cerr << e.what() << std::endl; + } catch (wren::RuntimeError& e) { + // Throw when something went wrong when executing + // the function. For example: a variable does not + // exist, or bad method name. + std::cerr << e.what() << std::endl; + } + + // If you are lazy, just do the following: + try { + wren::Method wrenMain = vm.find("main", "Main").func("main()"); + wrenMain(); + } catch (wren::Exception& e) { + // catch everything + std::cerr << e.what() << std::endl; + } +} +``` diff --git a/docs/content/Tutorial/custom_types.md b/docs/content/Tutorial/custom_types.md new file mode 100644 index 00000000..f3a060aa --- /dev/null +++ b/docs/content/Tutorial/custom_types.md @@ -0,0 +1,587 @@ +--- +title: 6. Custom types +--- + +# 6. Custom types + +Wren supports adding custom types. See [official documentation here](https://wren.io/embedding/calling-c-from-wren.html). This is done via foregin classes that can have member of static functions and fields. All of foreign classes added via WrenBind17 are wrapped in a custom wrapper (`wren::detail::ForeignObject`) that takes care of handling of instance of your custom C++ type. + +In order to use your custom types, you will have to register them as a foreign classes with foreign functions into the Wren VM. This is not done globally, therefore **you will have to do this for each of your Wren VM instances**. Wren VMs do not share their data between instances, nor the data about foreign classes. + +## 6.1. Type safety and shared_ptr + +**All custom types are handled as a `std::shared_ptr`.** Even if you pass your custom type into Wren as a copy, it will be moved into a shared pointer. Passing shared pointers into Wren has no extra mechanism and simply the shared pointer is kept inside of the wrapper. **This also ensures strong type safety. It is not possible to pass a C++ class instance of one type and get that variable back from Wren as a C++ class instance of an another type.** Doing so will throw the `wren::BadCast` exception. + +## 6.2 Passing values into Wren + +Passing values into Wren can be done in multiple ways, but they always end up as a shared pointer. Passing values happens when you call a Wren function from C++ and you pass some arguments, or when Wren calls a C++ foreign function that returns a type. + +**Returning a value from a C++ function or passing into a Wren function is done by the same mechanism**, therefore there are no differences. + +* Pass/return as a copy -> Moved into `std::shared_ptr` +* Pass/return as a pointer -> Pointer moved into `std::shared_ptr` with no deleter (won't free). +* Pass/return as a reference -> Handled as a pointer and pointer moved into `std::shared_ptr` with no deleter (won't free). +* Pass/return as a const pointer -> Pointer moved into `std::shared_ptr` with no deleter (won't free). +* Pass/return as a const reference -> Handled as copy and the instance is copied (duplicated) via it's copy constructor. + +TL;DR: Everything is a shared pointer. + +## 6.3 Adding custom modules + +To bind a C++ class, you will first need to create a new module. Creating a new module is done per VM instance. If you have multiple VMs in your application, they won't share the same modules. You would have to create the module for each of your VM instances. Copying modules between instances is not possible. + +```cpp +namespace wren = wrenbind17; // Alias + +wren::VM vm; + +// Create module called "mymodule" +wren::ForeignModule& m = vm.module("mymodule"); +``` + +You can create as many modules as you want. Additionally, calling the method `module(...)` multiple times with the same name won't create duplicates. For example: + +```cpp +wren::VM vm; + +wren::ForeignModule& m0 = vm.module("mymodule"); +wren::ForeignModule& m1 = vm.module("mymodule"); + +// m0 and m1 now point to the exact same module +``` + +{{< hint info >}} +**Note** + +Modules must be used via a reference `wren::ForeignModule& m = ...`. Copying modules is not allowed and causes compilation error. +{{% /hint %}} + +## 6.3 Adding custom classes + +Once you have a custom module, you can add classes to it, any classes. Your class does not have to have any specific functions, there are no requirements. Your class does not have to have a default constructor too, you can add anything. + + +Let's assume this is your C++ class: + + +```cpp +class Foo { +public: + Foo(const std::string& msg) { + ... + } + + void bar() { + + } + + int baz() const { + + } +}; +``` + +You can add it in the following way: + +```cpp +wren::VM vm; +auto& m = vm.module("mymodule"); + +// Add class "Foo" +auto& cls = m.klass("Foo"); + +// Define constructor (you can only specify one constructor) +cls.ctor(); + +// Add some methods +cls.func<&Foo::bar>("bar"); +cls.func<&Foo::baz>("baz"); +``` + +**The class functions (methods) are added as a template argument, not as the function argument.** This is due to the how Wren is built. Because of this implementation, you will also get extra performance -> the pointers to the class functions are optimized at compile time -> there are no lookup maps. + +And this is how you can use it: + +```js +import "mymodule" for Foo + +var foo = Foo.new("Message") +foo.bar(); +var i = foo.baz(); +``` + +Please note that **you don't have to manually create file "mymodule.wren"** and add all of your C++ foreign classes into it manually. Everything is automatically generated by the `wren::VM`. You can get the "raw" contents of the module that will be put into Wren by simply calling `.str()` on the module (e.g. `vm.module("mymodule").str();`). This will print the contents of that module as a Wren code with foreign classes. + + + +### 6.3.1 Handling function pointer ambiguity + +In case you have multiple functions with the same name, you will have to use `static_cast` to explicitly tell the compiler which function you want. For example: + +```cpp +class Foo { + const std::string& getMsg() const; + std::string& getMsg(); +}; + +wren::VM vm; +auto& m = vm.module("mymodule"); +auto& cls = vm.klass("Foo"); +cls.func(&Foo::getMsg)>("getMsg"); +``` + + +### 6.3.2 Static functions + +To add a static function, simply call the `funcStatic` instead of `func` as shown below: + +```cpp +class Log { + static void debug(const std::string& text); + static void error(const std::string& text); + static void info(const std::string& text); +}; + +wren::VM vm; +auto& m = vm.module("mymodule"); +auto& cls = m.klass("Log"); +cls.funcStatic<&Log::debug>("debug"); +cls.funcStatic<&Log::error>("error"); +cls.funcStatic<&Log::info>("info"); +``` + +### 6.3.3 Functions via external functions + + +Suppose you have some C++ class that you want to add to Wren, but you can't modify this class because it is from some other library. For example this C++ class can be from STL, or you want to add some custom behavior that does not exist in the class. In this case you can create a new function that accepts the class instance as the first parameter. + +```cpp +// Custom member function with "self" as this pointer +template +bool vectorContains(std::vector& self, const T& value) { + return std::find(self.begin(), self.end(), value) != self.end(); +} + +// Custom static function without any "self" +template +bool vectorFactory(const T& value) { + std::vector vec; + vec.push_back(value); + return vec; +} + +auto& cls = m.klass>("VectorInt"); +cls.ctor<>(); +cls.funcExt<&vectorContains>("contains"); +cls.funcStaticExt<&vectorFactory>("factory"); +``` + +```js +import "mymodule" for VectorInt + +var a = VectorInt.new() +var b = VectorInt.factory(456) // Calls vectorFactory +a.contains(123) // returns bool +``` + +"Ext" simply means that this is an external function and the first parameter **must** accept a reference to the class you are adding (unless it is a static function). **Do not mistake this with "extern" C++ keyword.** It has nothing to do with that. (Maybe there is a better word for it?) + +Additionally, if you look at the `vectorContains` function from above, there is no "this" pointer because this is not a member function. Instead, the "this" is provided as a custom parameter in the first position. This also works with `propExt` and `propReadonlyExt`. + +External functions added as `funcStaticExt` will be treated as static functions and do not accept the first parameter as "this". + +## 6.4 Abstract classes + +What if you want to pass an abstract class to Wren? You can't allocate it, but you can only pass it around as a reference or a pointer? Imagine a specific derived "Entity" class that has a common abstract/interface class? + +The only thing you have to do is to NOT to add constructor by not calling the `ctor` function. + +```cpp +wren::VM vm; +auto& m = vm.module("mymodule"); + +// Add class "Vec3" +auto& cls = m.klass("Entity"); +// cls.ctor<>(); Do not add constructor! +cls.func<&Entity::foo>("foo"); +``` + +## 6.5 Adding class varialbles + +There are two ways how to add C++ class variables to Wren. One is as a simple field and the other way is as a property. Adding as a field is done by accessing the pointer to that field (which is just a relative offset to "this"). Adding as a property is done via getters and setters (the setters are optional). To Wren, there is no difference between those two and are treated equally. + +### 6.5.1 Class variables as fields + +```cpp +struct Vec3 { + float x = 0; + float y = 0; + float z = 0; +}; + +wren::VM vm; +auto& m = vm.module("mymodule"); + +// Add class "Vec3" +auto& cls = m.klass("Vec3"); +cls.ctor<>(); +cls.var<&Vec3::x>("x"); +cls.var<&Vec3::y>("y"); +cls.var<&Vec3::z>("z"); +``` + +### 6.5.2 Class variables as properties + +```cpp +class Vec3 { +public: + float getX() const { return x; } + void setX(float value) { x = value; } + float getY() const { return y; } + void setY(float value) { y = value; } + float getZ() const { return z; } + void setZ(float value) { z = value; } +private: + float x = 0; + float y = 0; + float z = 0; +}; + +wren::VM vm; +auto& m = vm.module("mymodule"); + +// Add class "Vec3" +auto& cls = m.klass("Vec3"); +cls.ctor<>(); +cls.prop<&Vec3::getX, &Vec3::setX>("x"); +cls.prop<&Vec3::getY, &Vec3::setY>("y"); +cls.prop<&Vec3::getZ, &Vec3::setZ>("z"); +``` + +**The result from above:** + +Equivalent Wren code for both using `.var<&field>("name")` or `.prop<&getter, &setter>("name")`: + +```js +// Autogenerated +foreign class Vec3 { + construct new () {} + + foreign x + foreign x=(rhs) + + foreign y + foreign y=(rhs) + + foreign z + foreign z=(rhs) +} +``` + +And then simply use it in Wren as: + +```js +import "mymodule" for Vec3 + +var v = Vec3.new() +v.x = 1.23 +v.y = 0.0 +v.z = 42.42 +``` + +### 6.5.3 Read-only class variables + +To bind read-only variables you can use `varReadonly` function. This won't define a Wren setter and therefore the variable can be only read. + +```cpp +class Vec3 { +public: + Vec3(float x, float y, float z) {...} + + const float x; + const float y; + const float z; +}; + +wren::VM vm; +auto& m = vm.module("mymodule"); + +// Add class "Vec3" +auto& cls = m.klass("Vec3"); +cls.ctor<>(); +cls.varReadonly<&Vec3::x>("x"); +cls.varReadonly<&Vec3::y>("y"); +cls.varReadonly<&Vec3::z>("z"); +``` + +Equivalent Wren code: + +```js +// Autogenerated +foreign class Vec3 { + construct new () {} + + foreign x + + foreign y + + foreign z +} +``` + +And then simply use it in Wren as: + +```js +import "mymodule" for Vec3 + +var v = Vec3.new(1.1, 2.3, 3.3) +System.print("X value is: %(v.x)") // ok +v.x = 1.23 // error +``` + +For read-only properties, you can use `propReadonly` as shown below: + +```cpp +// Add class "Vec3" +auto& cls = m.klass("Vec3"); +cls.ctor<>(); +cls.propReadonly<&Vec3::getX>("x"); +cls.propReadonly<&Vec3::getY>("y"); +cls.propReadonly<&Vec3::getZ>("z"); +``` + + +### 6.5.4 Class variables via external properties + +Sometimes the property simply does not exist in the C++ class you want to use. So, you somehow need to add this into Wren without changing the original class code. One way to do it is through "external" functions. This is simply a function that is static and **must** accept the first parameter as a reference to the class instance. + +```cpp +static float getVec3X(Vec3& self) { + return self.x; +} + +static float getVec3Y(Vec3& self) { ... } +static float getVec3Z(Vec3& self) { ... } + +// Add class "Vec3" +auto& cls = m.klass("Vec3"); +cls.ctor<>(); +cls.propExtReadonly<&getVec3X>("x"); +cls.propExtReadonly<&getVec3Y>("y"); +cls.propExtReadonly<&getVec3Z>("z"); +``` + +### 6.5.5 Class static variables + +Static variables in Wren are not supported. However, you could cheat a bit with the following code: + +```cpp +class ApplicationGlobals { +public: + std::string APP_NAME = "Lorem Ipsum Donor"; +}; + +int main() { + ... + + wren::VM vm(...); + auto& m = vm.module("mymodule"); + + auto& cls = m.klass("ApplicationGlobals"); + cls.var<&ApplicationGlobals::APP_NAME>("APP_NAME"); + m.append("var Globals = ApplicationGlobals.new()\n"); + + return 0; +} +``` + +Wren code: + + +```js +import "mymodule" for Globals + +print("Name: %(Globals.APP_NAME)") +``` + +What does `m.append` do? It allows you to add arbitraty Wren code into the auto generated Wren code from your C++ classes. Anything you will put into the append function will appear at the bottom of the autogenerated code. Calling the append function multiple times is allowed, it will **not** override previous append call. In this case above, the ApplicationGlobals is created as an instance named Globals. So, from the user's perspective in the Wren code, it appears as a static member variable. The name **must start with a capital letter**, otherwise Wren will not allow you to import that variable. + + +## 6.6. Upcasting + +Upcasting is when you have a derived class `Enemy` and you would like to upcast it to `Entity`. An `Enemy` class is an `Entity`, but not the other way around. Remember, upcasting is getting the base class! + +This might be a problem when, for example, you have created a derived class inside of the Wren and you are passing it into some C++ function that accepts the base class. What you have to do is to tell the Wren what base classes it can be upcasted to. Consider the following example: + +```cpp +class Entity { + void update(); +}; + +class Enemy: public Entity { + ... +}; + +class EntityManager { + void add(std::shared_ptr entity) { + entities.push_back(entity); + } +}; + +Wren::VM vm; +auto& m = vm.module("game"); + +// Class Entity +auto& entityCls = m.klass("Entity"); +entityCls.func<&Entity::update>("update"); + +// Class Enemy +auto& enemyCls = m.klass("Enemy"); +// Classes won't automatically inherit functions and properties +// therefore you will have to explicitly add them for each +// derived class! +enemyCls.func<&Enemy::update>("update"); + +// Class EntityManager +auto& mngClass =m.klass("EntityManager"); +mngClass.func<&EntityManager::add>("add"); +``` + +Notice how we are adding two classes here as template parameters (`m.klass`). The first template parameter is the class you are binding, the second (and the next after that) template parameters are the classes for upcasting. You can use multiple classes as the base classes (`m.klass`), but that is only recommended if you know what you are doing. Make sure that you will also bind any inherited member functions to the derived class because this is not done automatically. + +And the Wren code: + +```js +import "game" for EntityManager, Enemy + +var manager = ... + +var e = Enemy.new() +e.update() +manager.add(e) // ok +``` + +{{< hint info >}} +**Note** + +Upcasting such as this only works when you want to accept a reference, pointer, or a shared_ptr of the base class. This won't work with plain value types. +{{< /hint >}} + +## 6.7. Class methods that throw + +You can catch the exception (and the exception message) inside of your Wren code. Consider the following C++ class that throws: + +```cpp +class MyCppClass { +public: + ... + void someMethod() { + throw std::runtime_error("hello"); + } +}; +``` + +And this is how you catch that exception in Wren: + +```js +var fiber = Fiber.new { + var i = MyCppClass.new() + i.someMethod() // C++ throws "hello" +} + +var error = fiber.try() +System.print("Caught error: " + error) // Prints "Caught error: hello" +``` + +## 6.8. Inheritance + +Wren does not support inheritacne of foreign classes, but there is a workaround. Consider the following C++ class: + +```cpp +class Entity { +public: + Entity() { ... } + virtual ~Entity() { ... } + + virtual void update() = 0; +}; +``` + +Now we want to have our own class in Wren: + +```js +import "game" for Entity + +class Enemy is Entity { // Not allowed by Wren :( + construct new (...) { + + } + + update() { + // Do something specific for Entity class + } +} +``` + +**This does not work.** You can't inherit from a foreign class. But, don't lose hope yet, there is a workaround. First, we need to create a C++ derived class of the base abstract class that overrides the update method. This is necessary so that we can call the proper Wren functions. + +```cpp +class WrenEntity: public Entity { +public: + // Pass the Wren class to the constructor + WrenEntity(wren::Variable derived) { + // Find all of the methods you want to "override" + // The number of arguments (_) or (_, _) does matter! + updateFn = derived.func("update(_)"); + } + + virtual ~WrenEntity() { + + } + + void update() override { + // Call the overriden Wren methods from + // the Wren class whenever you need to. + // Pass this class as the base class + updateFn(this); + } + +private: + // Store the Wren methods as class fields + wren::Method updateFn; +}; + +wren::VM vm; +auto& m = vm.module("game"); +auto& cls = m.klass("Entity"); +cls.ctor(); + +vm.runFromSource("main", ...); + +// Call the main function (see Wren code below) +auto res = vm.find("main", "Main").func("main()"); + +// Get the instance with Wren's specific functions +std::shared_ptr enemy = res.shared(); +``` + +And the following Wren code to be used with the code above: + +```js +import "game" for Entity + +class Enemy { + update (self) { + // self points to the base class! + } +} + +class Main { + static main () { + // Pass our custom Enemy class + // to the base Entity class into the constructor. + // The base class will call the necessary functions. + return Entity.new(Enemy.new()) + } +} +``` diff --git a/docs/content/Tutorial/customize.md b/docs/content/Tutorial/customize.md index ed499980..65e441a8 100644 --- a/docs/content/Tutorial/customize.md +++ b/docs/content/Tutorial/customize.md @@ -1,9 +1,11 @@ --- -title: Customize VM behavior -weight: 170 +title: 9. Customize VM --- -## Min heap and growth +# 9. Customize VM + + +## 9.1. Min heap and growth To control the minimal heap, heap growth, and initial heap, use the constructor to do so. Example: @@ -34,7 +36,7 @@ int main(...) { ``` -## Print function +## 9.2. Print function The print function is defined as: @@ -56,38 +58,45 @@ int main(...) { } ``` -## File loader function +## 9.3. File loader function -When loading other files via `import "hello" for Foo`, then VM will use a file loader function to read the file into a string. This will only happen once per file, even if the file is imported multiple times (Wren controls this). Please note that a function (as shown below) is already provided by default to the `wren::VM`. You don't need to set your own, it's optional. Also, this function does not override the loading of built-in modules (modules you add via `auto& m = vm.module(...);`) but instead this function will get called only after a built-in module with the specific name has not been found. Example: + +You can use your own custom mechanism for handling the imports. This is done by defining your own function of the following type: ```cpp -#include -namespace wren = wrenbind17; // Alias +typedef std::function& paths, + const std::string& name + )> LoadFileFn; +``` + +And using it as this: +```cpp int main(...) { - auto loadFileFn = [](const std::vector& paths, - const std::string& name) -> std::string { - - for (const auto& path : paths) { - const auto test = path + "/" + std::string(name) + ".wren"; - - std::ifstream t(test); - if (!t) - continue; - - std::string source(std::istreambuf_iterator(t), - std::istreambuf_iterator()); - return source; - } - - // To indicate that a file does not exist - // you must throw eny exception! But, returning - // an empty string will count as: - // "file exists but it's empty"! - throw wren::NotFound(); - }; - - wren::VM vm(); - vm.setLoadFileFunc(loadFileFn); + wren::VM vm({"./"}); + + const myLoader = []( + const std::vector& paths, + const std::string& name) -> std::string { + + // "paths" - This list comes from the + // first argument of the wren::VM constructor. + // + // "name" - The name of the import. + + // Return the source code in this function or throw an exception. + // For example you can throw wren::NotFound(); + + return ""; + }; + + vm.setLoadFileFunc(myLoader); } ``` + +{{< hint warning >}} +**Warning** + +Changing the loader function will also modify the `wren::VM::runFromModule` function. That function depends on the loader. The argument you pass into the `runFromModule` will become the `name` parameter in the loader. +{{< /hint >}} diff --git a/docs/content/Tutorial/exceptions.md b/docs/content/Tutorial/exceptions.md deleted file mode 100644 index 98720a48..00000000 --- a/docs/content/Tutorial/exceptions.md +++ /dev/null @@ -1,77 +0,0 @@ ---- -title: Exceptions -weight: 50 ---- - -WrenBind17 has a single base exception: `wrenbind17::Exception` (inherits `std::exception`) with the following derived exceptions: - -* `wrenbind17::NotFound` - Throw when trying to find a variable or a static class inside of a Wren via `wrenbind17::VM::find(module, name)`. -* `wrenbind17::BadCast` - When passing values into Wren, for example when trying to pass in a class that has not been registered. -* `wrenbind17::RuntimeError` - When calling a Wren method via `wrenbind17::Method::operator()`. - -Example of such runtime error: - -``` -Runtime error: Vector4 does not implement 'a=(_)'. - at: main:7 -``` - -{{% notice info %}} -Note that throwing a custom C++ exception will not transfer it's custom contents forward into the Wren code. The C++ exception is caught first on the C++ side, then a Wren error is generated with the message contents of that exception. This error is then thrown as `wrenbind17::RuntimeError` with the error message. Meaning, if you have a custom exception that contains more than the error message itself (for example, a HTTP exception with a status code and body contents), it will be lost. You will have to find a better way of handing such exceptions. Only the message itself is preserved. -{{% /notice %}} - -### Catch C++ exception in Wren - -```cpp -class MyCppClass { -public: - ... - void someMethod() { - throw std::runtime_error("hello"); - } -}; -``` - -```js -var fiber = Fiber.new { - var i = MyCppClass.new() - i.someMethod() // C++ throws "hello" -} - -var error = fiber.try() -System.print("Caught error: " + error) // Prints "Caught error: hello" -``` - -### Catch Wren exception in C++ - -```cpp -#include -namespace wren = wrenbind17; // Alias - -int main(...) { - wren::VM vm; - ... - - try { - wren::Method wrenMain = vm.find("main", "Main").func("main()"); - wrenMain(); - } catch (wren::NotFound& e) { - // Thows only if class "Main" has not been found - std::cerr << e.what() << std::endl; - } catch (wren::RuntimeError& e) { - // Throw when something went wrong when executing - // the function. For example: a variable does not - // exist, or bad method name. - std::cerr << e.what() << std::endl; - } - - // If you are lazy, just do the following: - try { - wren::Method wrenMain = vm.find("main", "Main").func("main()"); - wrenMain(); - } catch (wren::Exception& e) { - // catch everything - std::cerr << e.what() << std::endl; - } -} -``` diff --git a/docs/content/Tutorial/executing-code.md b/docs/content/Tutorial/execute_code.md similarity index 66% rename from docs/content/Tutorial/executing-code.md rename to docs/content/Tutorial/execute_code.md index bd86176a..c9e011ba 100644 --- a/docs/content/Tutorial/executing-code.md +++ b/docs/content/Tutorial/execute_code.md @@ -1,15 +1,12 @@ --- -title: 'Executing code' -weight: 30 +title: 5. Executing from file --- -Executing Wren code can be done in the following ways: +# 5. Executing from file -```cpp -// Execute from raw std::string and specify the module name manually. -// Can be any module name you want! -vm.runFromSource("main", "var i = 42"); +Executing Wren code can also be done in the following ways: +```cpp // Same as above, specify the module name but instead // of the source code you tell it where the file is located. vm.runFromFile("main", "path/to/some/main.wren"); @@ -19,6 +16,8 @@ vm.runFromFile("main", "path/to/some/main.wren"); vm.runFromModule("utils/libB"); ``` -{{% notice note %}} +{{< hint info >}} +**Note** + The `runFromModule` depends on the lookup paths you pass into the `wren::VM` constructor. The name of the module you want to run depends on these paths. Read the [Modules]({{< ref "modules.md" >}}) tutorial first. -{{% /notice %}} +{{% /hint %}} diff --git a/docs/content/Tutorial/callbacks.md b/docs/content/Tutorial/fn.md similarity index 89% rename from docs/content/Tutorial/callbacks.md rename to docs/content/Tutorial/fn.md index 37c46254..3a14d300 100644 --- a/docs/content/Tutorial/callbacks.md +++ b/docs/content/Tutorial/fn.md @@ -1,8 +1,10 @@ --- -title: Callbacks via Fn.new -weight: 130 +title: 10. Fn.new and callbacks --- +# 10. Fn.new and callbacks + + Let's say you have a GUI button widget and you want to have a specific action when a user clicks on it. ```cpp @@ -56,7 +58,7 @@ private: }; ``` -And then bind the class in the following way: +Bind the class in the following way: ```cpp wren::VM vm; @@ -65,7 +67,7 @@ auto& cls = m.klass("GuiButton"); cls.ctor(); ``` -And you can use it in the following way: +And finally the usage in Wren: ```js import "test" for GuiButton diff --git a/docs/content/Tutorial/hello-world.md b/docs/content/Tutorial/hello-world.md deleted file mode 100644 index b13231b0..00000000 --- a/docs/content/Tutorial/hello-world.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: 'Hello World' -weight: 20 ---- - -First, create a `wren::VM` instance, run the source code, find the method you want to run, and execute the method. In the example below, we are printing out a simple "Hello World" to the console. It is recommended that you create an alias from `wrenbind17` to `wren` so you don't have to type that many characters every time. I highly encourage you to **NOT** to use `using namespace wrenbind17;`! - -```cpp -#include -namespace wren = wrenbind17; // Alias - -int main(int argc, char *argv[]) { - const std::string code = R"( - class Main { - static main() { - System.print("Hello World!") - } - } - )"; - - // Create new VM - wren::VM vm; - - // Runs the code from the std::string as a "main" module - vm.runFromSource("main", code); - - // Find class Main in module main - wren::Variable mainClass = vm.find("main", "Main"); - - // Find function main() in class Main - wren::Method main = mainClass.func("main()"); - - // Execute the function - main(); - - return 0; -} -``` - -{{% notice note %}} -Remember to run your script code first! You won't be able to lookup any Wren variables or functions until the script code has been compiled and run via `runFromSource(...)`. -{{% /notice %}} - diff --git a/docs/content/Tutorial/hello_world.md b/docs/content/Tutorial/hello_world.md new file mode 100644 index 00000000..71d8f232 --- /dev/null +++ b/docs/content/Tutorial/hello_world.md @@ -0,0 +1,32 @@ +--- +title: 2. Hello World +--- + +# 2. Hello World + +First, create a `wren::VM` instance, run the source code, find the method you want to run, and execute the method. In the example below, we are printing out a simple "Hello World" to the console. It is recommended that you create an alias from `wrenbind17` to `wren` so you don't have to type that many characters every time. I highly encourage you to **NOT** to use `using namespace wrenbind17;`! + +This is the most simple hello world program. The only thing code does is to parse and run a Wren code (a simple print statement) from a simple string. This is not limited to only a single line of code, you can put in an entire contents of some Wren file. + +{{< hint info >}} +**Note** + +Calling `wren::VM::runFromSource(std::string, std::string)` will parse and run the code at the same time. +{{< /hint >}} + +```cpp +#include +namespace wren = wrenbind17; // Alias + +int main(int argc, char *argv[]) { + const std::string code = "System.print(\"Hello World!\")"; + + // Create new VM + wren::VM vm; + + // Runs the code from the std::string as a "main" module + vm.runFromSource("main", "System.print("Hello World!")"); + + return 0; +} +``` diff --git a/docs/content/Tutorial/inheritance.md b/docs/content/Tutorial/inheritance.md deleted file mode 100644 index ccb34afe..00000000 --- a/docs/content/Tutorial/inheritance.md +++ /dev/null @@ -1,98 +0,0 @@ ---- -title: Inheritance via composition -weight: 120 ---- - -Wren does not support inheritacne of foreign classes, but there is a workaround. Consider the following C++ class: - -```cpp -class Entity { -public: - Entity() { ... } - virtual ~Entity() { ... } - - virtual void update() = 0; -}; -``` - -Now we want to have our own class in Wren: - -```js -import "game" for Entity - -class Enemy is Entity { // Not allowed by Wren :( - construct new (...) { - - } - - update() { - // Do something specific for Entity class - } -} -``` - -**This does not work.** You can't inherit from a foreign class. But, don't lose hope yet, there is a workaround. First, we need to create a C++ derived class of the base abstract class that overrides the update method. This is necessary so that we can call the proper Wren functions. - -```cpp -class WrenEntity: public Entity { -public: - // Pass the Wren class to the constructor - WrenEntity(wren::Variable derived) { - // Find all of the methods you want to "override" - // The number of arguments (_) or (_, _) does matter! - updateFn = derived.func("update(_)"); - } - - virtual ~WrenEntity() { - - } - - void update() override { - // Call the overriden Wren methods from - // the Wren class whenever you need to. - // Pass this class as the base class - updateFn(this); - } - -private: - // Store the Wren methods as class fields - wren::Method updateFn; -}; - -wren::VM vm; -auto& m = vm.module("game"); -auto& cls = m.klass("Entity"); -cls.ctor(); - -vm.runFromSource("main", ...); - -// Call the main function (see Wren code below) -auto res = vm.find("main", "Main").func("main()"); - -// Get the instance with Wren's specific functions -std::shared_ptr enemy = res.shared(); -``` - -And the following Wren code to be used with the code above: - -```js -import "game" for Entity - -class Enemy { - update (self) { - // self points to the base class! - } -} - -class Main { - static main () { - // Pass our custom Enemy class - // to the base Entity class into the constructor. - // The base class will call the necessary functions. - return Entity.new(Enemy.new()) - } -} -``` - -With the example from above, if the methods are missing in the Wren's `Enemy` class, it will simply throw an exception, because `derived.find(signature)` throws `wren::NotFound` exception if no function with that signature has been found. - diff --git a/docs/content/Tutorial/install.md b/docs/content/Tutorial/install.md new file mode 100644 index 00000000..bce9907e --- /dev/null +++ b/docs/content/Tutorial/install.md @@ -0,0 +1,18 @@ +--- +title: 1. Installation +--- + +# 1. Installation + +You don't need to compile this library. **This library is a header only library**, all you have to do is to include the `#include ` header in your C++ project. However, you will need to compile and link the Wren VM, and adding an include path where the `` file is located. To see how to build and use the Wren library, see [Getting Started section here](http://wren.io/getting-started.html) from the official Wren documentation. + +**Optionally, you can build Wren with WrenBind17** by setting a CMake flag `WRENBIND17_BUILD_WREN` to `ON`. To do this, simply clone this repository and build it in the following way: + +```bash +git clone https://github.com/matusnovak/wrenbind17.git wrenbind17 +cd wrenbind17 +mkdir build + +cmake -B ./build -DWRENBIND17_BUILD_WREN=ON . +cmake --build ./build +``` diff --git a/docs/content/Tutorial/installation.md b/docs/content/Tutorial/installation.md deleted file mode 100644 index d25bf93e..00000000 --- a/docs/content/Tutorial/installation.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: 'Installation' -weight: 10 ---- - -You don't need to compile this library. This library is a header only and all you have to do is to include the `#include ` header in your C++ project. - -This library will need the Wren main header file: `` so make sure your build system has a search path for that header. You will also need to link the Wren library, otherwise you will get linker errors. To see how to build and use the Wren library, see [Getting Started section here](http://wren.io/getting-started.html) from the official Wren documentation. - -Optionally, you can build Wren with WrenBind17 by setting CMake flag `WRENBIND17_BUILD_WREN` to `ON`. To do this, simply clone this repository and use CMake in the following way: - -```bash -git clone https://github.com/matusnovak/wrenbind17.git -cd wrenbind17 -mkdir build -cd build -cmake .. -DWRENBIND17_BUILD_WREN=ON -cmake --build . -``` diff --git a/docs/content/Tutorial/lists.md b/docs/content/Tutorial/lists.md deleted file mode 100644 index e81a5b0f..00000000 --- a/docs/content/Tutorial/lists.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Lists -weight: 150 ---- - -## Pre-made STL vector and list bindings - -Lists are not implemented in this library natively. However, they are added as an optional classes that you can use. The `std::vector` and `std::list` are implemented via `wren::StdVectorBindings::bind(module, name);` and as `wren::StdListBindings::bind(module, name);`. For example: - -```cpp -Wren::VM vm; -auto& m = vm.module("std"); -wren::StdVectorBindings::bind(m, "VectorInt"); -``` - -And the Wren code: - -```js -import "std" for VectorInt - -var v = VectorInt.new() -v.add(42) // Push new value -v.insert(-1, 20) // Insert at the end -v.contains(42) // returns true -v.pop() // Remove last element and returns it -v.count // Returns the length/size -v.size() // Same as the count -v.clear() // Removes everything -v.removeAt(-2) // Removes the 2nd element from back and returns it -v[0] = 43 // Set specific index (negative indexes not supported!) -System.print("Second: %(v[1])") // Get specific index (no negative indexes!) -for (item in v) { // Supports iteration - System.print("Item: %(item)") // Prints individual elements -} -``` - -{{% notice note %}} -I highly recommend going through `wrenbind17/include/wrenbind17/std.hpp` to see how exactly this works. -{{% /notice %}} - -## Custom lists - -If you read through the [Iterator Protocol](http://wren.io/control-flow.html#the-iterator-protocol) section on official Wren documentation, then you know that you need to implement at least 2 functions: - -* `iterate(_)` -* `iteratorValue(_)` - -Let's implement all of them for `std::vector`! First we need the `iterate()` function. This function must accept already existing iterator or a null. To do this, we will use `std::variant` and an external function that will be bind to Wren via `funcExt`. - -```cpp -typedef typename std::vector::iterator Iterator; -typedef typename std::vector Vector; - -static std::variant iterate( - Vector& self, // this - std::variant other - ) { - - // Check if "other" variant is NOT nullptr - if (other.index() == 1) { - // Get the 2nd template, the iterator - auto it = std::get(other); - ++it; - if (it != self.end()) { - // Return the next position - return {it}; - } - - // Once we reach the end, we must return false - return {false}; - } else { - // No iterator supplied, the variant is null, - // then return the start of the vector - return {self.begin()}; - } -} -``` - -Next, we need the `iteratorValue()` function. This one is very simple: - -```cpp -static int iteratorValue(Vector& self, std::shared_ptr other) { - // You could replace the shared_ptr with a simple copy value. - // But that depends on you. - auto& it = *other; - return *it; -} -``` - -And then bind it! - -```cpp -wren::VM vm; -auto& m = vm.module("std"); -auto& cls = m.klass("VectorInt"); -cls.ctor<>(); -cls.funcExt<&iterate>("iterate"); -cls.funcExt<&iteratorValue>("iteratorValue"); -``` - -That's all you need to implement your custom list and use it inside of a for loop! - -## Custom lists and operator [] - -To get the operator [] working, you need two functions to set and to get the index. - -```cpp -typedef typename std::vector::iterator Iterator; -typedef typename std::vector Vector; - -static void setIndex(Vector& self, size_t index, int value) { - self[index] = value; -} - -static int getIndex(Vector& self, size_t index) { - return self[index]; -} -``` - -And then bind it! - -```cpp -wren::VM vm; -auto& m = vm.module("std"); -auto& cls = m.klass("VectorInt"); -cls.ctor<>(); -cls.funcExt<&getIndex>(wren::OPERATOR_GET_INDEX); -cls.funcExt<&setIndex>(wren::OPERATOR_SET_INDEX); -``` - -The enum values you supply instead of function names will create special bindings for overloading operators. Wren will see the following: - -```js -// Autogenerated -class VectorInt { - ... - foreign [index] // Get - foreign [index]=(other) // Set -} -``` diff --git a/docs/content/Tutorial/maps.md b/docs/content/Tutorial/maps.md deleted file mode 100644 index 023477f5..00000000 --- a/docs/content/Tutorial/maps.md +++ /dev/null @@ -1,261 +0,0 @@ ---- -title: Maps -weight: 151 ---- - -## Pre-made STL map and unordered_map bindings - -Native Wren maps cannot be accessed within C++. There is some work being done here: [wren-lang/wren#725](https://github.com/wren-lang/wren/pull/725) but at the moment it is not in the release version of Wren. As an alternative, WrenBind17 provides a sample wrapper for `std::map` and `std::unordered_map`. This also includes support for `std::variant` as the map value type. The provided wrappers are `StdMapBindings` and `StdUnorderedMapBindings` where K is the key type (for example an `std::string`) and the T is the value type which can be anything including a variant. - -```cpp -Wren::VM vm; -auto& m = vm.module("std"); -wren::StdUnorderedMapBindings::bind(m, "MapOfStrings"); -``` - -And the Wren code: - -```js -import "std" for MapOfStrings - -var map = MapOfStrings.new() - -// Set the value using [] operator -map["hello"] = "world" - -// Access the value -var value = map["hello"] - -// Exactly same as in std::map::operator[] -// If the key does not exist, then it is created -// using the default value. -// So "value2" becomes empty string! -var value2 = map["nonexisting_key"] - -// Removes value by key and returns the value removed. -// If the key does not exist, the returned value is null. -var removed = map.remove("hello") - -// Check if the key exists -if (map.containsKey("hello")) { - System.print("Key exists") -} - -// Clears the map, removing all elements. -// Same as std::map::clear() -map.clear() - -// Get the number of elements in the map. -// Both the function size() and the property count do the same thing. -var total = map.size() -var total = map.count - -// Check if the map is empty -if (map.empty()) { - System.print("There is nothing in the map!") -} - -// Iterate over the map. -// The map has an iterator of std::map::iterator which -// returns std::pair pairs. -// So to access the key you have to use the key property of the pair. -// And the same goes for the value. -// This is exactly the same behavior as iterating over the map in C++ -for (pair in map) { - System.print("Key: %(pair.key) value: %(pair.value)") -} -``` - -{{% notice note %}} -I highly recommend going through `wrenbind17/include/wrenbind17/std.hpp` to see how exactly this works. -{{% /notice %}} - -## Maps with variant value type - -Sometimes you need more than one type inside of the map, something like a Json. To do that, you can use the `std::variant` as the map mapped type. An example below. - -```cpp -typedef std::variant Multitype; - -class FooClass { -public: - ... - - void useMap(std::unordered_map& map) { - // Get the Multitype value by the key - auto& multitype = map["string"] - - // Get std::string from the variant. - // Because std::string is the 3rd template argument - // of the std::variant Multitype, then we - // need to use std::get to access the type! - auto& str = std::get<2>(multitype); - } -}; - -int main() { - Wren::VM vm; - auto& m = vm.module("std"); - wren::StdUnorderedMapBindings::bind(m, "MapOfMultitypes"); - - auto& cls = m.klass("FooClass"); - cls.func<&FooClass::useMap>("useMap"); - - vm.runFromSource(...); - - return 0; -} - -``` - -And the Wren code for the above map of variants: - -```js -import "std" for MapOfMultitypes, FooClass - -var map = MapOfMultitypes.new() -map["string"] = "world" -map["int"] = 123456 -map["null"] = null // Will become std::nullptr_t -map["boolean"] = true - -for (pair in map) { - System.print("Key: %(pair.key) value: %(pair.value)") -} - -// Pass the foo to some custom class -var foo = FooClass.new() -foo.useMap(map) -``` - - -## Custom maps - -To create a custom map you will need to implement the [Iterator Protocol](http://wren.io/control-flow.html#the-iterator-protocol) and the `[]` operator. - -Let's start with the basics, create a wren VM and add register the map (with the key and value types defined!) to the Wren. You can't create a generic map, Wren does not support that. If you want to have different map types with different key types or value types, you will need to do this multiple times. That's what `StdMapBindings` and `StdUnorderedMapBindings` are designed for. - -```cpp -typedef std::unordered_map MapOfInts; - -int main() { - wren::VM vm; - auto& m = vm.module("mymodule"); - auto& cls = m.klass("MapOfInts"); - cls.ctor(); // Empty default constructor - - // ... - - return 0; -} -``` - -Now, the iterator and iterator value. This is based on the Wren requirements to implement a class that can be iterated. See [Iterator Protocol](http://wren.io/control-flow.html#the-iterator-protocol) for more information. - -```cpp -static std::variant iterate( - MapOfInts& self, - std::variant other) { - - // If the variant holds "1" then the value being hold is a MapOfInts::iterator - if (other.index() == 1) { - auto it = std::get(other); - ++it; - if (it != self.end()) { - return {it}; - } - - return {false}; - } - // Otherwise the variant holds "0" therfore a null - else { - return {self.begin()}; - } -} - -// The "value_type" is the std::pair of the MapOfInts -static MapOfInts::value_type iteratorValue( - MapOfInts& self, - std::shared_ptr other) { - - // This simply returns the iterator value which is the std::pair - auto& it = *other; - return *it; -} -``` - -You will also need these two functions to access the key and the value type of the pairs during iteration. - -```cpp -// The "key_type" is the std::string and "mapped_type" is the int -static const MapOfInts::key_type& pairKey(MapOfInts::value_type& pair) { - return pair.first; -} - -static const MapOfInts::mapped_type& pairValue(MapOfInts::value_type& pair) { - return pair.second; -} -``` - -Furthemore accessing or setting the values by key using the operator `[]` can be done as the following: - -```cpp -static void setIndex(MapOfInts& self, const MapOfInts::key_type& key, MapOfInts::mapped_type value) { - self[key] = std::move(value); -} - -static MapOfInts::mapped_type& getIndex(MapOfInts& self, const MapOfInts::key_type& key) { - return self[key]; -} -``` - -You will have to then bind these functions above to the MapOfInts class. - -```cpp -typedef std::unordered_map MapOfInts; - -int main() { - wren::VM vm; - auto& m = vm.module("mymodule"); - auto& cls = m.klass("MapOfInts"); - cls.ctor(); // Empty default constructor - - // These two functions must be named exactly like this, Wren requires these names. - cls.funcExt<&iterate>("iterate"); - cls.funcExt<&iteratorValue>("iteratorValue"); - - // These two functions add the operator [] functionality. - cls.funcExt<&getIndex>(wren::ForeignMethodOperator::OPERATOR_GET_INDEX); - cls.funcExt<&setIndex>(wren::ForeignMethodOperator::OPERATOR_SET_INDEX); - - // Bind the iterator of this map, without this - // you cannot pass the iterator between Wren and C++ - auto& iter = m.klass("MapOfIntsIter"); - iter.ctor(); - - // Bind the pair of this map too. You need this - // so you can access the keys and values during iteration. - auto& pair = m.klass("MapOfIntsPair"); - pair.propReadonlyExt<&pairKey>("key"); - pair.propReadonlyExt<&pairValue>("value"); - - return 0; -} -``` - -Sample Wren usage: - -```js -import "mymodule" for MapOfInts - -var map = MapOfInts.new() - -map["first"] = 123 -map["second"] = 456 -var second = map["second"] - - -for (pair in map) { - System.print("Key: %(pair.key) value: %(pair.value)") -} -``` diff --git a/docs/content/Tutorial/modules.md b/docs/content/Tutorial/modules.md index dea06049..72164d74 100644 --- a/docs/content/Tutorial/modules.md +++ b/docs/content/Tutorial/modules.md @@ -1,11 +1,12 @@ --- -title: Modules -weight: 40 +title: 8. Modules and files --- -Wren support modularity (official documentation [here](http://wren.io/modularity.html)), but does not exactly work out of the box. WrenBind17 fills this gap by using a look-up paths. If you are familiar with Python, this is almost the same as the Python home path for loading modules. +# 8. Modules and files -When creating an instance of the VM, the first argument is a list of paths to use for lookup, for example: +Wren support modularity (official documentation [here](http://wren.io/modularity.html)), but does not exactly work out of the box. WrenBind17 fills this gap by adding a file load function that works using a list of look-up paths. + +If you are familiar with Python, this is almost the same as the Python home path for loading modules. ```cpp std::vector paths = { @@ -15,9 +16,9 @@ std::vector paths = { wren::VM vm(paths); ``` -**But**, it is highly advised to use absolute paths. You can use relative paths, but they will depend on your current working directory. The default value of the VM constructor is `{"./"}`. This means that by default the VM will look for files relative to your working directory. If there is a file named `libs/mylib.wren` in your working directory, then you can import that file as `import "libs/mylib" for XYZ`. +It is highly advised to use absolute paths. You can use relative paths, but they will depend on your current working directory. The default value of the VM constructor is `{"./"}`. This means that by default the VM will look for files relative to your working directory. If there is a file named `./libs/mylib.wren` in your working directory, then you can import that file as `import "libs/mylib" for XYZ`. -### Detailed explanation +## 8.1. Detailed explanation Consider the following program file structure: @@ -31,7 +32,7 @@ myprogram/ libB.wren ``` -You have three files: data/main.wren, data/libA.wren, and data/utils/libB.wren. Now, inside of your main, you might have something like this: +You have three files: `data/main.wren`, `data/libA.wren`, and `data/utils/libB.wren`. Now, inside of your main, you might have something like this: ```js // File: data/main.wren @@ -39,7 +40,7 @@ You have three files: data/main.wren, data/libA.wren, and data/utils/libB.wren. import "libA" for XYZ ``` -**This won't work by default.** Because of the default argument for the wren::VM constructor is `{"./"}`, the libA is being looked for inside of myprogram/ (assuming the current working directory is myprogram/). What you should do is to construct Wren::VM in the following way: +**This won't work by default.** Because of the default argument for the `wren::VM` constructor is `{"./"}`, the libA is being looked for inside of `myprogram/./.wren` (assuming the current working directory is `myprogram/`). What you should do is to construct `Wren::VM` in the following way: ```cpp std::vector paths = { @@ -50,12 +51,83 @@ wren::VM vm(paths); Only then the `import "libA" for XYZ` will work correctly. -**But**, relative imports do not work due to the design of the Wren! You will have to use absolute paths. So, how do you import myprogram/data/utils/libB.wren in myprogram/data/libA.wren? You simply use `import "utils/libB" for XYZ` in your code. +{{< hint warning >}} +**Warning** + +Relative imports do not work due to the design of the Wren! You will have to use absolute paths. So, how do you import `myprogram/data/utils/libB.wren` in `myprogram/data/libA.wren`? You simply use `import "utils/libB" for XYZ` in that file. +{{< /hint >}} -### Import ambiguity +### 8.1.1 Import ambiguity If you have two Wren source code files named exactly the same, but in two different lookup paths, only the first file will be loaded. The `std::vector` of paths you use as the first argument of `wren::VM` constructor is an ordered list of paths. The files are looked for in the order you define it. -### Module names +### 8.1.2. Module names for custom types Any module name is permitted as long as it can be a valid Wren string. Slashes such as `import "lib/utils/extras" for tokenize` is allowed, simply create a module as `auto& m = vm.module("lib/utils/extras");`. + +## 8.2. Custom import mechanism + +You can use your own custom mechanism for handling the imports. This is done by defining your own function of the following type: + +```cpp +typedef std::function& paths, + const std::string& name + )> LoadFileFn; +``` + +And using it as this: + +```cpp +int main(...) { + wren::VM vm({"./"}); + + const myLoader = []( + const std::vector& paths, + const std::string& name) -> std::string { + + // "paths" - This list comes from the + // first argument of the wren::VM constructor. + // + // "name" - The name of the import. + + // Return the source code in this function or throw an exception. + // For example you can throw wren::NotFound(); + + return ""; + }; + + vm.setLoadFileFunc(myLoader); +} +``` + +{{< hint warning >}} +**Warning** + +Changing the loader function will also modify the `wren::VM::runFromModule` function. That function depends on the loader. The argument you pass into the `runFromModule` will become the `name` parameter in the loader. +{{< /hint >}} + +## 8.3. Raw modules + +You can create any number of modules. For example: + +```cpp +wren::VM vm; +auto& m = vm.module("mymodule"); + +m.append(R"( + class Vec3 { + construct new (x, y, z) { + _x = x + _y = y + _z = z + } + } +)"); +``` + +Then the module can be used as: + +```js +import "mymodule" for Vec3 +``` diff --git a/docs/content/Tutorial/overload-operators.md b/docs/content/Tutorial/operators.md similarity index 51% rename from docs/content/Tutorial/overload-operators.md rename to docs/content/Tutorial/operators.md index a4a2e24f..6b2f3b13 100644 --- a/docs/content/Tutorial/overload-operators.md +++ b/docs/content/Tutorial/operators.md @@ -1,11 +1,38 @@ --- -title: Overload operators -weight: 80 +title: 7. Class operators --- -## Arithmetic and comparison operators +# 7. Class operators -Overloading operators is done same as binding any other methods. The only difference is that you have to use the [operator enumeration](https://matusnovak.github.io/wrenbind17/docs/doxygen/namespacewrenbind17.html#enum-foreignmethodoperator) instead of the name. But first, your C++ class must support the operators you want to bind, for example: +Operators can be added to your custom types, this is done via the `wren::ForeignMethodOperator` enumeration. + +This is a list of all supported operators by WrenBind17: + +| Operator | Enum Value | +| -------- | ---------- | +| Add (+) | OPERATOR_ADD | +| Subtract (-) | OPERATOR_SUB | +| Multiply (*) | OPERATOR_MUL | +| Divide (/) | OPERATOR_DIV | +| Unary negative (-) | OPERATOR_NEG | +| Modulo (%) | OPERATOR_MOD | +| Equal to (==) | OPERATOR_EQUAL | +| Not equal to (!=) | OPERATOR_NOT_EQUAL | +| Greater than (>) | OPERATOR_GT | +| Less than (<) | OPERATOR_LT | +| Greather than or equal (>=) | OPERATOR_GT_EQUAL | +| Less than or equal (<=) | OPERATOR_LT_EQUAL | +| Shift left (<<) | OPERATOR_SHIFT_LEFT | +| Shift right (>>) | OPERATOR_SHIFT_RIGHT | +| Binary and (&) | OPERATOR_AND | +| Binary xor (^) | OPERATOR_XOR | +| Binary or (\|) | OPERATOR_OR | +| Get by index [] | OPERATOR_GET_INDEX | +| Set by index [] | OPERATOR_SET_INDEX | + +## 7.1. Basic usage + +Adding arithemtic or comparison operators can be done in the following way. ```cpp class Vec3 { @@ -45,36 +72,37 @@ public: float y; float z; }; -``` -And then bind it in the following way: - -```cpp -// If you don't do this, the compiler will have no idea -// which operator to use when binding OPERATOR_SUB and OPERATOR_NEG. -// With this and static_cast you will explicitly tell the compiler -// which exact function to use. -typedef Vec3 (Vec3::*Vec3Sub)(const Vec3&) const; -typedef Vec3 (Vec3::*Vec3Neg)() const; - -// Bind the class and some basic functions/vars -auto& cls = m.klass("Vec3"); -cls.ctor(); // Constructor -cls.var<&Vec3::x>("x"); -cls.var<&Vec3::y>("y"); -cls.var<&Vec3::z>("z"); - -// Bind the operators -cls.func<&Vec3::operator+ >(wren::OPERATOR_ADD); -cls.func(&Vec3::operator-)>(wren::OPERATOR_SUB); -cls.func(&Vec3::operator-)>(wren::OPERATOR_NEG); -cls.func<&Vec3::operator* >(wren::OPERATOR_MUL); -cls.func<&Vec3::operator/ >(wren::OPERATOR_DIV); -cls.func<&Vec3::operator== >(wren::OPERATOR_EQUAL); -cls.func<&Vec3::operator!= >(wren::OPERATOR_NOT_EQUAL); +int main(...) { + wren::VM vm; + ... + + // If you don't do this, the compiler will have no idea + // which operator to use when binding OPERATOR_SUB and OPERATOR_NEG. + // With this and static_cast you will explicitly tell the compiler + // which exact function to use. + typedef Vec3 (Vec3::*Vec3Sub)(const Vec3&) const; + typedef Vec3 (Vec3::*Vec3Neg)() const; + + // Bind the class and some basic functions/vars + auto& cls = m.klass("Vec3"); + cls.ctor(); // Constructor + cls.var<&Vec3::x>("x"); + cls.var<&Vec3::y>("y"); + cls.var<&Vec3::z>("z"); + + // Bind the operators + cls.func<&Vec3::operator+ >(wren::OPERATOR_ADD); + cls.func(&Vec3::operator-)>(wren::OPERATOR_SUB); + cls.func(&Vec3::operator-)>(wren::OPERATOR_NEG); + cls.func<&Vec3::operator* >(wren::OPERATOR_MUL); + cls.func<&Vec3::operator/ >(wren::OPERATOR_DIV); + cls.func<&Vec3::operator== >(wren::OPERATOR_EQUAL); + cls.func<&Vec3::operator!= >(wren::OPERATOR_NOT_EQUAL); +} ``` -And the Wren code: +Afterwards you can use it in the following way: ```js import "test" for Vec3 @@ -83,39 +111,19 @@ var b = Vec3.new(4.0, 5.0, 6.0) var c = a + b ``` -Here is a list of all supported operators: +{{< hint info >}} +**Note** -| Operator | Enum Value | -| -------- | ---------- | -| Add (+) | OPERATOR_ADD | -| Subtract (-) | OPERATOR_SUB | -| Multiply (*) | OPERATOR_MUL | -| Divide (/) | OPERATOR_DIV | -| Unary negative (-) | OPERATOR_NEG | -| Modulo (%) | OPERATOR_MOD | -| Equal to (==) | OPERATOR_EQUAL | -| Not equal to (!=) | OPERATOR_NOT_EQUAL | -| Greater than (>) | OPERATOR_GT | -| Less than (<) | OPERATOR_LT | -| Greather than or equal (>=) | OPERATOR_GT_EQUAL | -| Less than or equal (<=) | OPERATOR_LT_EQUAL | -| Shift left (<<) | OPERATOR_SHIFT_LEFT | -| Shift right (>>) | OPERATOR_SHIFT_RIGHT | -| Binary and (&) | OPERATOR_AND | -| Binary xor (^) | OPERATOR_XOR | -| Binary or (\|) | OPERATOR_OR | -| Get by index [] | OPERATOR_GET_INDEX | -| Set by index [] | OPERATOR_SET_INDEX | +If you are using Visual Studio and trying to bind operator `<` then you might get an error: `error C2833: 'operator >' is not a recognized operator or type`. This happens because the compiler is unable to understand `cls.func<&Vec3::operator>>(wren::OPERATOR_GT)`. Simply, put `( )` around the operator like this: `cls.func<(&Vec3::operator>)>(...)`. +{{< /hint >}} -{{% notice info %}} -If you are using Visual Studio and trying to bind operator `<` then you might get an error: `error C2833: 'operator >' is not a recognized operator or type`. This happens because the compiler is unable to understand `cls.func<&Vec3::operator>>(wren::OPERATOR_GT)`. Simply, put `(...)` around the operator like this: `cls.func<(&Vec3::operator>)>(wren::OPERATOR_GT)`. That will fix the problem. -{{% /notice %}} +{{< hint info >}} +**Note** -{{% notice warning %}} -Using `*=`, `-=`, `+=`, or `/=` is not allowed. Wren does not support these assignment operators, and results in Wren compilation error. -{{% /notice %}} +Using `*=`, `-=`, `+=`, or `/=` is not allowed. Wren does not support these assignment operators and results in Wren compilation error. +{{< /hint >}} -## Operator with multiple types +## 7.2. Operator with multiple types Consider the following C++ class: @@ -132,7 +140,7 @@ public: }; ``` -You have two operators but the second one only accepts a single value. This can be useful when you want to, for example, multiply a 3D vector with a constant value. But this can be a problem when binding these two operators to Wren. You can't bind them both, but you can use `std::variant<>` instead. +You have two operators but the second one only accepts a single value. This can be useful when you want to, for example, multiply a 3D vector with a constant value. This can be a problem when binding these two operators to Wren. You can't bind them both, but you can use `std::variant<>` instead. Create a new function in the following way: @@ -147,16 +155,22 @@ Vec3 operator * (const std::variant& var) const { } } -auto& cls = m.klass("Vec3"); -cls.ctor(); -cls.var<&Vec3::x>("x"); -cls.var<&Vec3::y>("y"); -cls.var<&Vec3::z>("z"); - -// Optional typedef to explicitly select the correct operator with std::variant -typedef Vec3 (Vec3::*Vec3Mul)(const std::variant&) const; -// Bind the function -cls.func(&Vec3::operator*)>(wren::OPERATOR_MUL); +int main(...) { + wren::VM vm; + ... + + auto& cls = m.klass("Vec3"); + cls.ctor(); + cls.var<&Vec3::x>("x"); + cls.var<&Vec3::y>("y"); + cls.var<&Vec3::z>("z"); + + // Optional typedef to explicitly select the correct + // operator with the std::variant + typedef Vec3 (Vec3::*Vec3Mul)(const std::variant&) const; + // Bind the function + cls.func(&Vec3::operator*)>(wren::OPERATOR_MUL); +} ``` Then, insie of Wren, you can do the following: diff --git a/docs/content/Tutorial/raw-modules.md b/docs/content/Tutorial/raw-modules.md deleted file mode 100644 index 7c32b2b5..00000000 --- a/docs/content/Tutorial/raw-modules.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -title: Raw Modules -weight: 110 ---- - -Modules can be created in the following way: - -```cpp -wren::VM vm; -auto& m = vm.module("mymodule"); - -auto& cls = m.klass("Entity"); -cls.func<&Entity::foo>("foo"); -... -``` - -But, you are also able to create your own "raw" modules. - -```cpp -wren::VM vm; -auto& m = vm.module("mymodule"); - -m.append(R"( - class Vec3 { - construct new (x, y, z) { - ... - } - } -)"); -``` - -Anything you add via `m.append(...)` will be loaded into the Wren when you import that specific module. Anything you put inside of `append(...)` will always be appended at the end of the module code. Meaning, if you bind some C++ classes, the raw string(s) you append to that module will always be located after the C++ class foreign code. You can append as much code as you want, until you run out of system memory. - -These modules can be loaded in the Wren as the following: - -```js -import "mymodule" for Foo, Vec3 -``` diff --git a/docs/content/Tutorial/stl.md b/docs/content/Tutorial/stl.md new file mode 100644 index 00000000..319c5cea --- /dev/null +++ b/docs/content/Tutorial/stl.md @@ -0,0 +1,617 @@ +--- +title: 11. STL Containers +--- + +# 11. STL Containers + +## 11.1. Optionals + +The `std::optional` will be converted into a null or the type that it can hold. This also works when you call C++ function from Wren that accepts `std::optional`. You can either call that function with `null` or with the type `T`. + +### 11.1.1. Limitations + +Passing `std::optional` via non-const reference is not allowed. Also passing as a pointer or a shared pointer does not work either. Only passing as a plain type (copy) or a const reference is allowed. + +## 11.2. Variants + +Using `std::variant` is nothing special. When you pass it into Wren, what happens is that this library will check what type is being held by the variant, and then it will pass it into the Wren code. The Wren will not get the variant instace, but the value it holds! For example, passing `std::variant` will push either bool or int into Wren. + +The same goes for getting values from Wren as variant. Suppose you have a C++ member function that accepts the variant of `std::variant` then when you call this member function in Wren as `foo.baz(true)` or `foo.baz(42)` it will work, but `foo.baz("Hello")` won't work because the variant does not accept the string! + +Consider the following example: + +```cpp +class Foo { +public: + Foo() { + ... + } + + void baz(const std::variant& value) { + switch (value.index()) { + case 0: { + // Is bool! + const auto std::get(value); + } + case 1: { + // Is string! + const auto std::get(value); + } + default: { + // This should never happen. + } + } + } +} + +wren::VM vm = ...; +auto& m = vm.module(...); +auto& cls = m.klass("Foo"); +cls.func<&Foo::baz>("baz"); // Nothing special, just like any other functions +``` + +And the Wren code: + +```js +import "test" for Foo + +var foo = Foo.new() +foo.baz(false) // ok +foo.baz("Hello World") // ok +foo.baz(123.456) // error +``` + +### 11.2.1. Limitations + +Passing `std::variant` via non-const reference is not allowed. The following code will not work. Also passing as a pointer or a shared pointer does not work either. Only passing as a plain type (copy) or a const reference is allowed. + +```cpp +class Foo { +public: + Foo() { + ... + } + + void baz(std::variant& value) { + ... + } +} + +wren::VM vm = ...; +auto& m = vm.module(...); +auto& cls = m.klass("Foo"); +cls.func<&Foo::baz>("baz"); // Will not work +``` + +## 11.3. Sequences + +WrenBind17 supports the following sequence containers: `std::vector`, `std::list`, `std::deque`, and `std::set`. By default all of them are converted into native Wren lists. This means that when you pass (or some C++ function returns) any of these containers, **they are converted into Wren lists.** **Any modification to that list in Wren has no effect on the C++ container** passed/returned. Wren lists are not the same object as the STL containers. + +However, you can add this container to Wren VM as a foreign class. In that case the instance of the C++ container you pass into Wren will become a foreign class, therefore modifying the "list" (a class in reality) will also modify the C++ container -> they are the same object. + +This only works if you pass the container (or return from a C++ function) via a non-cost reference, pointer, or a shared pointer. Passing (or returning) via copy will create a copy of that C++ container. This check (whether to convert it to a native list or as a foreign class instance) happens at the runtime. + +Consider this following table. + +| Pass/return type | Added as a foreign class | Not added as a foreign class | +| ---------------- | --------------------------- | ------------------------------- | +| Pass by a copy | Copy of the container and pushed to Wren as a foreign class | Converted to native list | +| Pass by a reference | Pushed to Wren as a foreign class with no copy | BadCast exception | +| Pass by a const reference | Copy of the container and pushed to Wren as a foreign class | Converted to native list | +| Pass by a (const) pointer | Pushed to Wren as a foreign class with no copy | BadCast exception | +| Pass by a shared pointer | Pushed to Wren as a foreign class with no copy | BadCast exception | + +### 11.3.1. Native lists + +By default all of `std::vector`, `std::list`, `std::deque`, and `std::set` are converted to Wren lists. This also works the other way around -> getting a list either by calling a C++ function that accepts a list or returning a list by calling a Wren function. + +For example, you can return this native list into a `std::vector` of `std::variant` type. + +```js +class Main { + static main() { + return [null, 123, true, "Hello World"] + } +} +``` + +```cpp +wren::VM vm; + +vm.runFromSource("main", /* code from above */); +auto func = vm.find("main", "Main").func("main()"); + +auto res = func(); +assert(res.isList()); +auto vec = res.as>>(); + +assert(vec.size() == 4); +assert(std::get(vec[0]) == nullptr); +assert(std::get(vec[1]) == 123.0); +assert(std::get(vec[2]) == true); +assert(std::get(vec[3]) == "Hello World"); +``` + +{{< hint info >}} +**Note** + +In the above example we are getting `std::vector>`. If you add this type as a foreign class, the above example would run in the same way. This is because if you add this type as a foreign class, that only affects pushing that type to Wren. To not to use native lists, you will have to do something like this: + +```js +list = VectorOfVariant.new() +list.push(null) +list.push(123) +list.push(true) +list.push("Hello World") +return list +``` +{{< /hint >}} + + +### 11.3.2. Lists as foreign classes + +If you wish to add the container of some specific type as a foreign class, you can use the following method to do so: + +```cpp +Wren::VM vm; +auto& m = vm.module("std"); +wren::StdVectorBindings::bind(m, "VectorInt"); +``` + +The `wren::StdVectorBindings` is just a fancy wrapper that adds functions into the class. I highly recommend going through the `wrenbind17/include/wrenbind17/std.hpp` file to see how exactly this works. + +And the usage of that in Wren: + +```js +import "std" for VectorInt + +var v = VectorInt.new() +v.add(42) // Push new value +v.insert(-1, 20) // Insert at the end +v.contains(42) // returns true +v.pop() // Remove last element and returns it +v.count // Returns the length/size +v.size() // Same as the count +v.clear() // Removes everything +v.removeAt(-2) // Removes the 2nd element from back and returns it +v[0] = 43 // Set specific index (negative indexes not supported!) +System.print("Second: %(v[1])") // Get specific index (no negative indexes!) +for (item in v) { // Supports iteration + System.print("Item: %(item)") // Prints individual elements +} +``` + +### 11.3.3. Custom list from scratch + + +If you read through the [Iterator Protocol](http://wren.io/control-flow.html#the-iterator-protocol) section on official Wren documentation, then you know that you need to implement at least 2 functions: + +* `iterate(_)` +* `iteratorValue(_)` + +Let's implement all of them for `std::vector`! First we need the `iterate()` function. This function must accept already existing iterator or a null. To do this, we will use `std::variant` and an external function that will be bind to Wren via `funcExt`. + +```cpp +typedef typename std::vector::iterator Iterator; +typedef typename std::vector Vector; + +static std::variant iterate( + Vector& self, // this + std::variant other + ) { + + // Check if "other" variant is NOT nullptr + if (other.index() == 1) { + // Get the 2nd template, the iterator + auto it = std::get(other); + ++it; + if (it != self.end()) { + // Return the next position + return {it}; + } + + // Once we reach the end, we must return false + return {false}; + } else { + // No iterator supplied, the variant is null, + // then return the start of the vector + return {self.begin()}; + } +} +``` + +Next, we need the `iteratorValue()` function. This one is very simple: + +```cpp +static int iteratorValue(Vector& self, std::shared_ptr other) { + // You could replace the shared_ptr with a simple copy value. + // But that depends on you. + auto& it = *other; + return *it; +} +``` + +And then bind it! + +```cpp +wren::VM vm; +auto& m = vm.module("std"); +auto& cls = m.klass("VectorInt"); +cls.ctor<>(); +cls.funcExt<&iterate>("iterate"); +cls.funcExt<&iteratorValue>("iteratorValue"); +``` + +That's all you need to implement your custom list and use it inside of a for loop! + +### 11.3.4. Custom lists and operator [] + +To get the operator [] working, you need two functions to set and to get the index. + +```cpp +typedef typename std::vector::iterator Iterator; +typedef typename std::vector Vector; + +static void setIndex(Vector& self, size_t index, int value) { + self[index] = value; +} + +static int getIndex(Vector& self, size_t index) { + return self[index]; +} +``` + +And then bind it! + +```cpp +wren::VM vm; +auto& m = vm.module("std"); +auto& cls = m.klass("VectorInt"); +cls.ctor<>(); +cls.funcExt<&getIndex>(wren::OPERATOR_GET_INDEX); +cls.funcExt<&setIndex>(wren::OPERATOR_SET_INDEX); +``` + +The enum values you supply instead of function names will create special bindings for overloading operators. Wren will see the following: + +```js +// Autogenerated +class VectorInt { + ... + foreign [index] // Get + foreign [index]=(other) // Set +} +``` + +## 11.4. Maps + +WrenBind17 supports the following key-value containers: `std::map` and `std::unordered_map`. By default all of them are converted into native Wren maps. This means that when you pass any of these containers, **they are converted into Wren maps.** **Any modification to that map in Wren has no effect on the C++ container** passed. Wren maps are not the same object as the STL containers. + +{{< hint danger >}} +**It is not possible to convert a native Wren map into a C++ map.** This is a limitation of the Wren language. However, you can use `wren::Map` that will hold a reference to the Wren native map. You can use this class to retrieve values, remove keys, check if key exists, or get the size of the map. +{{< /hint >}} + +You can add the C++ map container to Wren VM as a foreign class. In that case the instance of the C++ container you pass into Wren will become a foreign class, therefore modifying the "map" (a class in reality) will also modify the C++ container -> they are the same object. + +This only works if you pass the container via a non-cost reference, pointer, or a shared pointer. Passing (or returning) via copy will create a copy of that C++ container. This check (whether to convert it to a native map or as a foreign class instance) happens at the runtime. + +Consider this following table. + +| Pass/return type | Added as a foreign class | Not added as a foreign class | +| ---------------- | --------------------------- | ------------------------------- | +| Pass by a copy | Copy of the container and pushed to Wren as a foreign class | Converted to native map | +| Pass by a reference | Pushed to Wren as a foreign class with no copy | BadCast exception | +| Pass by a const reference | Copy of the container and pushed to Wren as a foreign class | Converted to native map | +| Pass by a (const) pointer | Pushed to Wren as a foreign class with no copy | BadCast exception | +| Pass by a shared pointer | Pushed to Wren as a foreign class with no copy | BadCast exception | + +### 11.4.1. Native maps + +As mentioned above, it is not possible to get a native map from Wren and convert it into STL container. This is because the Wren low level API does not allow iterating over the map. Therefore, WrenBind17 provides `wren::Map` container that works on top of `wren::Handle` (it is a reference and affects the garbage collector). + +You can use `wren::Map` to get values via key, remove keys, check if key exists, or get the size of the entire map. Example code below. + +```js +class Main { + static main() { + return { + "first": 42, + "second": true, + "third": "Hello World", + "fourth": null + } + } + + static other(map) { + // Do something with the map + } +} +``` + +```cpp +wren::VM vm; + +vm.runFromSource("main", code); +auto func = vm.find("main", "Main").func("main()"); + +auto res = func(); +res.is(); // Returns true +res.isMap(); // Returns true + +auto map = res.as(); + +map.count(); // Returns 4 + +map.contains(std::string("first")); // Returns true +map.contains(std::string("fifth")); // Returns false + +map.get(std::string("first")); // Returns 42 +map.get(std::string("second")); // Returns true +map.get(std::string("third")); // Returns "Hello World" +map.get(std::string("fourth")); // Returns nullptr +map.get(std::string("fourth")); // Throws wren::BadCast + +map.erase(std::string("first")); // Returns true +map.erase(std::string("fifth")); // Returns false +map.count(); // 3 + +map.get(std::string("first")); // Throws wren::NotFound + +auto other = vm.find("main", "Main").func("other(_)"); + +other(map); // Pass the map to some other function +``` + +### 11.4.2. Maps as foreign classes + +If you wish to add the container of some specific type as a foreign class, you can use the following method to do so: + +```cpp +Wren::VM vm; +auto& m = vm.module("std"); +wren::StdUnorderedMapBindings::bind(m, "MapOfStrings"); +``` + +And the usage of that in Wren: + +```js +import "std" for MapOfStrings + +var map = MapOfStrings.new() + +// Set the value using [] operator +map["hello"] = "world" + +// Access the value +var value = map["hello"] + +// Exactly same as in std::map::operator[] +// If the key does not exist, then it is created +// using the default value. +// So "value2" becomes empty string! +var value2 = map["nonexisting_key"] + +// Removes value by key and returns the value removed. +// If the key does not exist, the returned value is null. +var removed = map.remove("hello") + +// Check if the key exists +if (map.containsKey("hello")) { + System.print("Key exists") +} + +// Clears the map, removing all elements. +// Same as std::map::clear() +map.clear() + +// Get the number of elements in the map. +// Both the function size() and the property count do the same thing. +var total = map.size() +var total = map.count + +// Check if the map is empty +if (map.empty()) { + System.print("There is nothing in the map!") +} + +// Iterate over the map. +// The map has an iterator of std::map::iterator which +// returns std::pair pairs. +// So to access the key you have to use the key property of the pair. +// And the same goes for the value. +// This is exactly the same behavior as iterating over the map in C++ +for (pair in map) { + System.print("Key: %(pair.key) value: %(pair.value)") +``` + + +### 11.4.3. Maps with variant value type + +Sometimes you need more than one type inside of the map, something like a Json. To do that, you can use the `std::variant` as the map mapped type. An example below. + +```cpp +typedef std::variant Multitype; + +class FooClass { +public: + ... + + void useMap(std::unordered_map& map) { + // Get the Multitype value by the key + auto& multitype = map["string"] + + // Get std::string from the variant. + // Because std::string is the 3rd template argument + // of the std::variant Multitype, then we + // need to use std::get to access the type! + auto& str = std::get<2>(multitype); + } +}; + +int main() { + Wren::VM vm; + auto& m = vm.module("std"); + wren::StdUnorderedMapBindings::bind(m, "MapOfMultitypes"); + + auto& cls = m.klass("FooClass"); + cls.func<&FooClass::useMap>("useMap"); + + vm.runFromSource(...); + + return 0; +} + +``` + +And the Wren code for the above map of variants: + +```js +import "std" for MapOfMultitypes, FooClass + +var map = MapOfMultitypes.new() +map["string"] = "world" +map["int"] = 123456 +map["null"] = null // Will become std::nullptr_t +map["boolean"] = true + +for (pair in map) { + System.print("Key: %(pair.key) value: %(pair.value)") +} + +// Pass the foo to some custom class +var foo = FooClass.new() +foo.useMap(map) +``` + + +### 11.4.4. Custom maps + +To create a custom map you will need to implement the [Iterator Protocol](http://wren.io/control-flow.html#the-iterator-protocol) and the `[]` operator. + +Let's start with the basics, create a wren VM and bind the map (with the key and value types defined!) to the Wren. You can't create a generic map, Wren does not support that. If you want to have different map types with different key types or value types, you will need to do this multiple times. That's what `StdMapBindings` and `StdUnorderedMapBindings` are designed for. + +```cpp +typedef std::unordered_map MapOfInts; + +int main() { + wren::VM vm; + auto& m = vm.module("mymodule"); + auto& cls = m.klass("MapOfInts"); + cls.ctor(); // Empty default constructor + + // ... + + return 0; +} +``` + +Now, the iterator and iterator value. This is based on the Wren requirements to implement a class that can be iterated. See [Iterator Protocol](http://wren.io/control-flow.html#the-iterator-protocol) for more information. + +```cpp +static std::variant iterate( + MapOfInts& self, + std::variant other) { + + // If the variant holds "1" then the value being hold is a MapOfInts::iterator + if (other.index() == 1) { + auto it = std::get(other); + ++it; + if (it != self.end()) { + return {it}; + } + + return {false}; + } + // Otherwise the variant holds "0" therfore a null + else { + return {self.begin()}; + } +} + +// The "value_type" is the std::pair of the MapOfInts +static MapOfInts::value_type iteratorValue( + MapOfInts& self, + std::shared_ptr other) { + + // This simply returns the iterator value which is the std::pair + auto& it = *other; + return *it; +} +``` + +You will also need these two functions to access the key and the value type of the pairs during iteration. + +```cpp +// The "key_type" is the std::string and "mapped_type" is the int +static const MapOfInts::key_type& pairKey(MapOfInts::value_type& pair) { + return pair.first; +} + +static const MapOfInts::mapped_type& pairValue(MapOfInts::value_type& pair) { + return pair.second; +} +``` + +Furthemore accessing or setting the values by key using the operator `[]` can be done as the following: + +```cpp +static void setIndex(MapOfInts& self, const MapOfInts::key_type& key, MapOfInts::mapped_type value) { + self[key] = std::move(value); +} + +static MapOfInts::mapped_type& getIndex(MapOfInts& self, const MapOfInts::key_type& key) { + return self[key]; +} +``` + +You will have to then bind these functions above to the MapOfInts class. + +```cpp +typedef std::unordered_map MapOfInts; + +int main() { + wren::VM vm; + auto& m = vm.module("mymodule"); + auto& cls = m.klass("MapOfInts"); + cls.ctor(); // Empty default constructor + + // These two functions must be named exactly like this, Wren requires these names. + cls.funcExt<&iterate>("iterate"); + cls.funcExt<&iteratorValue>("iteratorValue"); + + // These two functions add the operator [] functionality. + cls.funcExt<&getIndex>(wren::ForeignMethodOperator::OPERATOR_GET_INDEX); + cls.funcExt<&setIndex>(wren::ForeignMethodOperator::OPERATOR_SET_INDEX); + + // Bind the iterator of this map, without this + // you cannot pass the iterator between Wren and C++ + auto& iter = m.klass("MapOfIntsIter"); + iter.ctor(); + + // Bind the pair of this map too. You need this + // so you can access the keys and values during iteration. + auto& pair = m.klass("MapOfIntsPair"); + pair.propReadonlyExt<&pairKey>("key"); + pair.propReadonlyExt<&pairValue>("value"); + + return 0; +} +``` + +Sample Wren usage: + +```js +import "mymodule" for MapOfInts + +var map = MapOfInts.new() + +map["first"] = 123 +map["second"] = 456 +var second = map["second"] + + +for (pair in map) { + System.print("Key: %(pair.key) value: %(pair.value)") +} +``` + diff --git a/docs/content/Tutorial/types.md b/docs/content/Tutorial/types.md new file mode 100644 index 00000000..46ea6372 --- /dev/null +++ b/docs/content/Tutorial/types.md @@ -0,0 +1,61 @@ +--- +title: 4. Supported types +--- + +# 4. Supported types + +## 4.1. List of supported types and conversion + +This is a list of supported types and how they are converted between C++ and Wren. Not everything can be mapped exactly (for example, integers are casted into doubles), so there are some compromises. + +| C++ type | Wren type | Return value from `Any` | +| -------- | --------- | ------------------------ | +| signed char (int8_t) | number (64-bit float) | `.as()` | +| unsigned char (uint8_t) | number (64-bit float) | `.as()` | +| signed short (int16_t) | number (64-bit float) | `.as()` | +| unsigned short (uint16_t) | number (64-bit float) | `.as()` | +| signed int (int32_t) | number (64-bit float) | `.as()` | +| unsigned int (uint32_t) | number (64-bit float) | `.as()` | +| signed long long (int64_t) | number (64-bit float) | `.as()` | +| unsigned long long (uint64_t) | number (64-bit float) | `.as()` | +| float | number (64-bit float) | `.as()` | +| double | number (64-bit float) | `.as()`| +| char | number (64-bit float) | `.as()` | +| char[N] | string | Not possible to get raw char array from Wren, use std::string | +| char* | string | Not possible to get raw char pointer from Wren, use std::string | +| std::string | string | `.as()` | +| std::shared_ptr | T or null | `.as()` or `.as()` or `.shared()` | +| T (custom types) | foregin class of T | `.as()` (if copy is supported) or `.as()` or `.as()` or `.shared()` | + +## 4.2. Unsupported types + +| C++ type | Reason | +| -------- | ------ | +| std::unique_ptr | How would it work if you want to get `std::unique_ptr&` ? Not possible to implement. | +| std::queue | Can't iterate over the elements without modifying the queue itself. | + +## 4.3. STL Containers + +The following std containers are supported and will be converted to a Wren type. Read the [11. STL containers]({{< ref "stl.md" >}}) section to understand more about STL containers and how to use them. + +{{< hint warning >}} +**Warning** + +By default the following STL containers below will be converted to a native type. If you add/bind any of these containers (except optional or variant) as a foregin class via `vm.module(...).klass>("VectorInt")` then that type will no longer be treated as a native type. Only the type you have registered, for example `std::vector` will be treated as such, other types of the same container, for example `std::vector` will still be treated as a native list. + +Whether the STD container is converted a Wren native list/map or into a Wren foreign class is checked at runtime. + +Read the [11. STL containers]({{< ref "types.md" >}}) section to understand how it works. +{{< /hint >}} + +| C++ type | Wren type | Return value from `Any` | +| -------- | --------- | ------------------------ | +| std::variant | A or B or C | `.as()` or `.as>()` | +| std::optional | T or null | `.as()` (only if not null) or `.as>()` | +| std::vector | native list of T | `.as>()` | +| std::list | native list of T | `.as>()` | +| std::deque | native list of T | `.as>()` | +| std::set | native list of T | `.as>()` | +| std::unordered_set | native list of T | `.as>()` | +| std::map | native map of T | `.as>()` | +| std::unordered_map | native map of T | `.as>()` | diff --git a/docs/content/Tutorial/upcasting.md b/docs/content/Tutorial/upcasting.md deleted file mode 100644 index f52bad3a..00000000 --- a/docs/content/Tutorial/upcasting.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -title: Upcasting -weight: 140 ---- - -Upcasting is when you have a derived class `Enemy` and you would like to upcast it to `Entity`. An `Enemy` class is an `Entity`, but not the other way around. Remember, upcasting is getting the base class! - -But, this might be a problem when, for example, you have created a derived class inside of the Wren, and you are passing it into some C++ function that accepts the base class. What you have to do is to tell the Wren what base classes it can be upcasted to. Consider the following example: - -```cpp -class Entity { - void update(); -}; - -class Enemy: public Entity { - ... -}; - -class EntityManager { - void add(std::shared_ptr entity) { - entities.push_back(entity); - } -}; - -Wren::VM vm; -auto& m = vm.module("game"); - -// Class Entity -auto& entityCls = m.klass("Entity"); -entityCls.func<&Entity::update>("update"); - -// Class Enemy -auto& enemyCls = m.klass("Enemy"); -// Classes won't automatically inherit functions and properties -// therefore you will have to explicitly add them for each -// derived class! -enemyCls.func<&Enemy::update>("update"); - -// Class EntityManager -auto& mngClass =m.klass("EntityManager"); -mngClass.func<&EntityManager::add>("add"); -``` - -Notice how we are adding two classes here as template parameters: `m.klass`. The first template parameter is the class you are binding, the second (and the next after that) template parameters are the classes for upcasting. You can use multiple classes as the base classes: `m.klass`, but that is only recommended if you know what you are doing. Make sure that you will also bind any inherited member functions to the derived class. This is not done automatically. - -And the Wren code: - -```js -import "game" for EntityManager, Enemy - -var manager = ... - -var e = Enemy.new() -e.update() -manager.add(e) // ok -``` - -{{% notice info %}} -Upcasting such as this only works when you want to accept a reference, pointer, or a shared_ptr of the base class. This won't work with plain value types. -{{% /notice %}} diff --git a/docs/content/Tutorial/variant.md b/docs/content/Tutorial/variant.md deleted file mode 100644 index 4e3456b7..00000000 --- a/docs/content/Tutorial/variant.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Pass multiple types to C++ -weight: 160 ---- - -## Basic example - -Using `std::variant` is nothing special. When you pass it into Wren, what happens is that this library will check what type is being held by the variant, and then it will pass it into the Wren code. **The Wren will not get the variant instace, but the value it holds!** For example, passing `std::variant` will push either bool or int into Wren. - -The same goes for getting values from Wren as variant. Suppose you have a C++ member function that accepts the variant of `std::variant` then when you call this member function in Wren as `foo.set(true)` or `foo.set(42)` it will work, but `foo.set("Hello")` won't work because the variant does not accepts the string! - -```cpp -class Foo { -public: - Foo() { - ... - } - - void baz(const std::variant& value) { - switch (value.index()) { - case 0: { - // Is bool! - const auto std::get(value); - } - case 1: { - // Is string! - const auto std::get(value); - } - default: { - // This should never happen. - } - } - } -} - -wren::VM vm = ...; -auto& m = vm.module(...); -auto& cls = m.klass("Foo"); -cls.func<&Foo::baz>("baz"); // Nothing special, just like any other functions -``` - -And then inside of Wren: - -```js -import "test" for Foo - -var foo = Foo.new() -foo.baz(false) // ok -foo.baz("Hello World") // ok -foo.baz(123.456) // error -``` - -## Limitations - -Passing `std::variant` via **non-const reference** is not allowed. The following will **not** work: - -```cpp -class Foo { -public: - Foo() { - ... - } - - void baz(std::variant& value) { - ... - } -} - -wren::VM vm = ...; -auto& m = vm.module(...); -auto& cls = m.klass("Foo"); -cls.func<&Foo::baz>("baz"); // Nothing special, just like any other functions -``` diff --git a/docs/content/_index.md b/docs/content/_index.md index 6f2c567f..d562a4bc 100644 --- a/docs/content/_index.md +++ b/docs/content/_index.md @@ -1,45 +1,31 @@ ---- -title: WrenBind17 ---- - # WrenBind17 -WrenBind17 is a C++17 wrapper for [Wren programming language](http://wren.io/). This project was heavily inspired by [pybind11](https://github.com/pybind/pybind11) and by [Wren++](https://github.com/Nelarius/wrenpp). This library is header only and does not need any compilation steps. Simply include the `` header in your application and you are good to go! +[![build](https://github.com/matusnovak/wrenbind17/workflows/build/badge.svg?branch=master)](https://github.com/matusnovak/wrenbind17/actions) + +WrenBind17 is a C++17 wrapper for [Wren programming language](http://wren.io/). This project was heavily inspired by [pybind11](https://github.com/pybind/pybind11) and by [Wren++](https://github.com/Nelarius/wrenpp). This library is header only and does not need any compilation steps. Simply include the WrenBind17 header ``, link the Wren library, and you are good to go. ## Features * Header only. * Works with Visual Studio 2017, MinGW-w64, Linux GCC, and Apple Clang on Mac OSX. * C++17 so you don't need to use `decltype()` on class methods to bind them to Wren. -* [Foreign modules are automatically generated for you]({{< ref "Tutorial/hello-world.md" >}}). You don't need to write the extra foreign classes in separate file. +* Foreign modules are automatically generated for you. You don't need to write the extra foreign classes in separate file. * **Supports strict type safety.** You won't be able to pass just any variable from Wren back to the C++, preventing you getting segmentation faults. -* Objects are wrapped in `std::shared_ptr` so you have easier access when passing objects around. +* **Objects are wrapped in std::shared_ptr so you have easier access when passing objects around.** This also enables easy object lifetime management. * Easy binding system inspired by [pybind11](https://github.com/pybind/pybind11). -* [Works with exceptions]({{< ref "Tutorial/exceptions.md" >}}). -* [Upcasting to base types when passing C++ instances]({{< ref "Tutorial/upcasting.md" >}}). +* Works with exceptions. +* Upcasting to base types when passing C++ instances. * Memory leak tested. -* Supports `std::variant`. -* Supports `std::vector` and `std::list` via helper classes (optional). -* [Easy binding of operators]({{< ref "Tutorial/overload-operators.md" >}}) such as `+`, `-`, `[]`, etc. -* [Long but easy to follow tutorial]({{< ref "Tutorial/installation.md" >}}). -* [Supports Fn.new{}]({{< ref "Tutorial/callbacks.md" >}}). -* [Supports inheritance (a workaround)]({{< ref "Tutorial/inheritance.md" >}}). -* [Supports modularity via look-up paths]({{< ref "Tutorial/modules.md" >}}). -* [Supports passing variables by move]({{< ref "Tutorial/call-wren.md" >}}) +* Supports STL containers such as `std::variant`, `std::optional`, `std::vector`, `std::list`, `std::deque`, `std::set`, `std::unordered_set`, `std::map`, `std::unordered_map` which can be handled either natively or as a foreign class. +* Easy binding of operators such as `+`, `-`, `[]`, etc. +* Long but easy to follow tutorial ([here](https://matusnovak.github.io/wrenbind17/tutorial/)). +* Supports native lists and native maps. +* Supports Fn.new{}. +* Supports inheritance (a workaround). +* Supports modularity via look-up paths. +* Supports passing variables by move. ## Limitations * Passing by a reference to a Wren function will create a copy. Use a pointer if you do not wish to create copies and maintain single instance of a given class. This does not affect C++ member functions that return a reference, in that case it will be treated exactly same as a pointer. - -## Not yet implemented - -* Lambdas (very tricky due to passing function pointers, most likely not ever be implemented). -* `..`, `...`, and `is` operator binding. -* Helper classes for binding `std::queue`, `std::deque`, `std::stack`, `std::set`, `std::unordered_set`, `std::map`, and `std::unordered_map`. - -## Documentation - -Tutorials can be found [**here**]({{< ref "Tutorial/installation.md" >}}) - -And the autogenerated API documentation via Doxygen [**here**]({{< ref "Modules/group__wrenbind17.md" >}}) - +* STL containers `std::unique_ptr`, `std::queue`, `std::stack` are not supported. diff --git a/docs/content/menu.md b/docs/content/menu.md new file mode 100644 index 00000000..68d9fca6 --- /dev/null +++ b/docs/content/menu.md @@ -0,0 +1,22 @@ +--- +headless: true +bookMenuLevels: 2 +--- + * [Home]({{< relref "/" >}}) + * [Tutorial]({{< relref "/Tutorial" >}}) + * [1. Installation]({{< relref "/Tutorial/install.md" >}}) + * [2. Hello World]({{< relref "/Tutorial/hello_world.md" >}}) + * [3. Call Wren function]({{< relref "/Tutorial/call_wren.md" >}}) + * [4. Supported types]({{< ref "/Tutorial/types.md" >}}) + * [5. Executing from file]({{< ref "/Tutorial/execute_code.md" >}}) + * [6. Custom types]({{< ref "/Tutorial/custom_types.md" >}}) + * [7. Class operators]({{< ref "/Tutorial/operators.md" >}}) + * [8. Modules and files]({{< ref "/Tutorial/modules.md" >}}) + * [9. Customize VM]({{< ref "/Tutorial/customize.md" >}}) + * [10. Fn.new and callbacks]({{< ref "/Tutorial/fn.md" >}}) + * [11. STL containers]({{< ref "/Tutorial/stl.md" >}}) + * Api Documentation + * [Classes]({{< relref "/Classes" >}}) + * [Namespaces]({{< relref "/Namespaces" >}}) + * [Modules]({{< relref "/Modules" >}}) + * [Files]({{< relref "/Files" >}}) diff --git a/docs/themes/hugo-book b/docs/themes/hugo-book new file mode 160000 index 00000000..615400b3 --- /dev/null +++ b/docs/themes/hugo-book @@ -0,0 +1 @@ +Subproject commit 615400b3b74772d2b81fef17ab8773b6e9627692 diff --git a/docs/themes/hugo-theme-learn b/docs/themes/hugo-theme-learn deleted file mode 160000 index 51dbdcf4..00000000 --- a/docs/themes/hugo-theme-learn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 51dbdcf4aaef01d02e78a6ea76b2a6fde5842b55 diff --git a/include/wrenbind17/any.hpp b/include/wrenbind17/any.hpp index 77b248b6..840f2012 100644 --- a/include/wrenbind17/any.hpp +++ b/include/wrenbind17/any.hpp @@ -1,9 +1,9 @@ #pragma once -#include #include "handle.hpp" -#include "push.hpp" #include "pop.hpp" +#include "push.hpp" +#include /** * @ingroup wrenbind17 @@ -11,288 +11,214 @@ namespace wrenbind17 { /** * @ingroup wrenbind17 + * @brief A return value when calling a Wren function (alias Any) + * @see Any + * @details This extends the lifetime of the Wren object (handle). As long as + * this ReturnValue instance exists the Wren object will exist. + * @note This variable can safely outlive the wrenbind17::VM class. If that happens + * then functions of this class will throw wrenbind17::RuntimeError exception. This + * holder will not try to free the Wren variable if the VM has been terminated. You + * don't have to worry about the lifetime of this holder. (uses weak pointers). */ - class Any { + class ReturnValue { public: - class Content { - public: - virtual ~Content() { - } - virtual const std::type_info& getTypeid() const = 0; - }; - - template class Data : public Content { - public: - template Data(Arg arg) : value(std::move(arg)) { - } - virtual ~Data() = default; - const std::type_info& getTypeid() const override { - return typeid(T); - } - T& get() { - return value; - } - const T& get() const { - return value; - } - - private: - T value; - }; - - inline Any() - : content(nullptr) { - } - - inline explicit Any(WrenVM* vm, const bool value) - : vm(vm), - type(WrenType::WREN_TYPE_BOOL), - content(new Data(value)) { - } - - inline explicit Any(WrenVM* vm, const double value) - : vm(vm), - type(WrenType::WREN_TYPE_NUM), - content(new Data(value)) { - } - - inline explicit Any(WrenVM* vm, std::string value) - : vm(vm), - type(WrenType::WREN_TYPE_STRING), - content(new Data(std::move(value))) { - } - - inline explicit Any(WrenVM* vm, std::nullptr_t value) - : vm(vm), - type(WrenType::WREN_TYPE_NULL), - content(nullptr) { - } - - inline explicit Any(WrenVM* vm, void* value) - : vm(vm), - type(WrenType::WREN_TYPE_FOREIGN), - content(new Data(value)) { + ReturnValue() = default; + explicit ReturnValue(const WrenType type, Handle handle) : type(type), handle(std::move(handle)) { } + ~ReturnValue() = default; - inline Any(const Any& other) = delete; - inline Any(Any&& other) noexcept { + ReturnValue(const ReturnValue& other) = delete; + ReturnValue(ReturnValue&& other) noexcept { swap(other); } - inline Any& operator=(const Any& other) = delete; - inline Any& operator=(Any&& other) noexcept { + + ReturnValue& operator=(const ReturnValue& other) = delete; + ReturnValue& operator=(ReturnValue&& other) noexcept { if (this != &other) { swap(other); } return *this; } - inline void swap(Any& other) noexcept { - std::swap(vm, other.vm); + + void swap(ReturnValue& other) noexcept { std::swap(type, other.type); - std::swap(content, other.content); + std::swap(handle, other.handle); } - inline virtual ~Any() = default; - - template inline typename std::enable_if::value, T>::type as() const { - if (empty() || type != WREN_TYPE_FOREIGN) - throw BadCast("Bad cast when getting value from Wren expected instance"); - using Type = typename std::remove_const::type>::type; - return *detail::getSlotForeign(vm, contentCast().get()).get(); + /*! + * Returns the handle that this instance owns + */ + const Handle& getHandle() const { + return handle; } - template inline typename std::enable_if::value, T>::type as() const { - if (empty()|| type != WREN_TYPE_FOREIGN) - throw BadCast("Bad cast when getting value from Wren expected instance"); - using Type = typename std::remove_const::type>::type; - return detail::getSlotForeign(vm, contentCast().get()).get(); + /*! + * Returns the handle that this instance owns + */ + Handle& getHandle() { + return handle; } - template inline typename std::enable_if::value, T>::type as() const { - if (empty() || type != WREN_TYPE_FOREIGN) - throw BadCast("Bad cast when getting value from Wren expected instance"); - using Type = typename std::remove_const::type>::type; - return detail::getSlotForeign(vm, contentCast().get()); + /*! + * @brief The raw wren type held by this instance + */ + WrenType getType() const { + return type; } - template inline std::shared_ptr shared() const { - if (empty() || type != WREN_TYPE_FOREIGN) - throw BadCast("Bad cast when getting value from Wren expected instance"); - using Type = typename std::remove_const::type>::type; - return detail::getSlotForeign(vm, contentCast().get()); + /*! + * @brief Check if the value held is some specific C++ type + * @note If the value held is a Wren numeric type then checking for any + * C++ integral or floating type will return true. + */ + template bool is() const { + if (type == WREN_TYPE_NULL) { + return false; + } + if (const auto vm = handle.getVmWeak().lock()) { + wrenEnsureSlots(vm.get(), 1); + wrenSetSlotHandle(vm.get(), 0, handle.getHandle()); + using Type = typename std::remove_reference::type>::type; + return detail::is(vm.get(), 0); + } else { + throw RuntimeError("Invalid handle"); + } } - template inline bool is() const { - if (empty()) - return false; - if (type != WREN_TYPE_FOREIGN) - return false; - using Type = typename std::remove_const::type>::type; - const auto foreign = reinterpret_cast(contentCast().get()); - return foreign->hash() == typeid(Type).hash_code(); + bool isMap() const { + return type == WREN_TYPE_MAP; } - inline bool empty() const { - return content == nullptr; + bool isList() const { + return type == WREN_TYPE_LIST; } - private: - template - const Data& contentCast() const { - if (content == nullptr || content->getTypeid() != typeid(T)) { + /*! + * @brief Returns the value + * @note If the value held is a Wren numeric type then getting for any + * C++ integral or floating type will result in a cast from a double to that type. + * @throws RuntimeError if this instance is invalid (constructed via the default constructor) + * @throws BadCast if the type required by specifying the template argument does not match the type held + * @tparam T the type you want to get + * @see shared() + */ + template T as() { + if (type == WREN_TYPE_NULL) { throw BadCast("Bad cast when getting value from Wren"); } - return *static_cast*>(content.get()); + if (const auto vm = handle.getVmWeak().lock()) { + wrenEnsureSlots(vm.get(), 1); + wrenSetSlotHandle(vm.get(), 0, handle.getHandle()); + return detail::PopHelper::f(vm.get(), 0); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Returns the value as a shared pointer + * @note Only works for custom C++ classes (foreign classes) that have been bound to the VM. + * @throws RuntimeError if this instance is invalid (constructed via the default constructor) + * @throws BadCast if the type required by specifying the template argument does not match the type held + * @tparam T the type of the std::shared_ptr you want to get + * @see as() + */ + template std::shared_ptr shared() { + return as>(); } - WrenVM* vm = nullptr; + private: WrenType type = WrenType::WREN_TYPE_NULL; - std::unique_ptr content; + Handle handle; }; - template <> inline bool Any::is() const { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NUM; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_BOOL; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_NULL; } - template <> inline bool Any::is() const { + template <> inline bool ReturnValue::is() const { return type == WREN_TYPE_STRING; } - template <> inline std::nullptr_t Any::as() const { - if (!empty()) { - throw BadCast("Return value is not null"); + template <> inline std::nullptr_t ReturnValue::as() { + if (!is()) { + throw BadCast("Return value is not a null"); } return nullptr; } +#endif - template <> inline int8_t Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline char Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline short Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline int Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline long Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline long long Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline unsigned char Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline unsigned short Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline unsigned int Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline unsigned long Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline unsigned long long Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline float Any::as() const { - return static_cast(contentCast().get()); - } - - template <> inline double Any::as() const { - return contentCast().get(); - } - - template <> inline bool Any::as() const { - return contentCast().get(); - } - - template <> inline std::string Any::as() const { - return contentCast().get(); - } + /** + * @ingroup wrenbind17 + * @see ReturnValue + * @brief An alias of ReturnValue class + */ + using Any = ReturnValue; +#ifndef DOXYGEN_SHOULD_SKIP_THIS template <> inline Any detail::getSlot(WrenVM* vm, const int idx) { const auto type = wrenGetSlotType(vm, 0); - switch (type) { - case WrenType::WREN_TYPE_BOOL: - return Any(vm, wrenGetSlotBool(vm, 0)); - case WrenType::WREN_TYPE_NUM: - return Any(vm, wrenGetSlotDouble(vm, 0)); - case WrenType::WREN_TYPE_STRING: - return Any(vm, std::string(wrenGetSlotString(vm, 0))); - case WrenType::WREN_TYPE_FOREIGN: - return Any(vm, wrenGetSlotForeign(vm, 0)); - default: - return Any(vm, nullptr); + if (type == WREN_TYPE_NULL) { + return Any(); } + return Any(type, Handle(getSharedVm(vm), wrenGetSlotHandle(vm, idx))); } +#endif } // namespace wrenbind17 diff --git a/include/wrenbind17/caller.hpp b/include/wrenbind17/caller.hpp index 3b45c534..2a46f9e1 100644 --- a/include/wrenbind17/caller.hpp +++ b/include/wrenbind17/caller.hpp @@ -1,8 +1,8 @@ #pragma once -#include "push.hpp" -#include "pop.hpp" #include "index.hpp" +#include "pop.hpp" +#include "push.hpp" /** * @ingroup wrenbind17 @@ -70,8 +70,8 @@ namespace wrenbind17 { } }; - template <> inline void ForeginMethodReturnHelper::push( - WrenVM* vm, int index, const std::string& ret) { + template <> + inline void ForeginMethodReturnHelper::push(WrenVM* vm, int index, const std::string& ret) { PushHelper::f(vm, index, ret); } @@ -79,21 +79,16 @@ namespace wrenbind17 { PushHelper::f(vm, index, ret); } - template struct ForeignMethodCaller { - template static void callFrom(WrenVM* vm, detail::index_list) { + template static void callFrom(WrenVM* vm, detail::index_list) { auto self = PopHelper::f(vm, 0); - //R ret = (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); - //PushHelper::f(vm, 0, ret); + // R ret = (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); ForeginMethodReturnHelper::push( - vm, - 0, - (self->*Fn)(PopHelper::type>:: - f(vm, Is + 1)...) - ); + vm, 0, (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...)); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { @@ -101,20 +96,16 @@ namespace wrenbind17 { } } - template static void callFrom( - WrenVM* vm, detail::index_list) { + template + static void callFrom(WrenVM* vm, detail::index_list) { auto self = PopHelper::f(vm, 0); - //R ret = (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); - //PushHelper::f(vm, 0, ret); + // R ret = (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); ForeginMethodReturnHelper::push( - vm, - 0, - (self->*Fn)(PopHelper::type>:: - f(vm, Is + 1)...) - ); + vm, 0, (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...)); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { @@ -124,13 +115,13 @@ namespace wrenbind17 { }; template struct ForeignMethodCaller { - template static void - callFrom(WrenVM* vm, detail::index_list) { + template + static void callFrom(WrenVM* vm, detail::index_list) { auto self = PopHelper::f(vm, 0); (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { @@ -138,13 +129,13 @@ namespace wrenbind17 { } } - template static void callFrom( - WrenVM* vm, detail::index_list) { + template + static void callFrom(WrenVM* vm, detail::index_list) { auto self = PopHelper::f(vm, 0); (self->*Fn)(PopHelper::type>::f(vm, Is + 1)...); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { @@ -154,20 +145,15 @@ namespace wrenbind17 { }; template struct ForeignMethodExtCaller { - template static void callFrom(WrenVM* vm, detail::index_list) { + template static void callFrom(WrenVM* vm, detail::index_list) { auto self = PopHelper::f(vm, 0); - //R ret = (*Fn)(*self, PopHelper::type>::f(vm, Is + 1)...); - //PushHelper::f(vm, 0, ret); + // R ret = (*Fn)(*self, PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); ForeginMethodReturnHelper::push( - vm, - 0, - (*Fn)(*self, - PopHelper::type>:: - f(vm, Is + 1)...) - ); + vm, 0, (*Fn)(*self, PopHelper::type>::f(vm, Is + 1)...)); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { @@ -177,13 +163,13 @@ namespace wrenbind17 { }; template struct ForeignMethodExtCaller { - template static void callFrom( - WrenVM* vm, detail::index_list) { + template + static void callFrom(WrenVM* vm, detail::index_list) { auto self = PopHelper::f(vm, 0); (*Fn)(*self, PopHelper::type>::f(vm, Is + 1)...); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { @@ -193,18 +179,14 @@ namespace wrenbind17 { }; template struct ForeignFunctionCaller { - template static void callFrom(WrenVM* vm, detail::index_list) { - //R ret = (*Fn)(PopHelper::type>::f(vm, Is + 1)...); - //PushHelper::f(vm, 0, ret); + template static void callFrom(WrenVM* vm, detail::index_list) { + // R ret = (*Fn)(PopHelper::type>::f(vm, Is + 1)...); + // PushHelper::f(vm, 0, ret); ForeginMethodReturnHelper::push( - vm, - 0, - (*Fn)(PopHelper::type>:: - f(vm, Is + 1)...) - ); + vm, 0, (*Fn)(PopHelper::type>::f(vm, Is + 1)...)); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { @@ -214,11 +196,11 @@ namespace wrenbind17 { }; template struct ForeignFunctionCaller { - template static void callFrom(WrenVM* vm, detail::index_list) { + template static void callFrom(WrenVM* vm, detail::index_list) { (*Fn)(PopHelper::type>::f(vm, Is + 1)...); } - template static void call(WrenVM* vm) { + template static void call(WrenVM* vm) { try { callFrom(vm, detail::index_range<0, sizeof...(Args)>()); } catch (...) { diff --git a/include/wrenbind17/foreign.hpp b/include/wrenbind17/foreign.hpp index 9e901c28..00311910 100644 --- a/include/wrenbind17/foreign.hpp +++ b/include/wrenbind17/foreign.hpp @@ -36,6 +36,7 @@ namespace wrenbind17 { }; /** * @ingroup wrenbind17 + * @brief Holds information about a foreign function of a foreign class */ class ForeignMethod { public: @@ -51,14 +52,23 @@ namespace wrenbind17 { virtual void generate(std::ostream& os) const = 0; + /*! + * @brief Returns the name of the method + */ const std::string& getName() const { return name; } + /*! + * @brief Returns the raw pointer of this method + */ WrenForeignMethodFn getMethod() const { return method; } + /*! + * @brief Returns true if this method is marked as static + */ bool getStatic() const { return isStatic; } @@ -71,6 +81,7 @@ namespace wrenbind17 { /** * @ingroup wrenbind17 + * @brief Holds information about a foreign property of a foreign class */ class ForeignProp { public: @@ -91,18 +102,30 @@ namespace wrenbind17 { os << " foreign " << name << "=(rhs)\n"; } + /*! + * @brief Returns the name of this property + */ const std::string& getName() const { return name; } + /*! + * @brief Returns the pointer to the raw function for settings this property + */ WrenForeignMethodFn getSetter() { return setter; } + /*! + * @brief Returns the pointer to the raw function for getting this property + */ WrenForeignMethodFn getGetter() { return getter; } + /*! + * @brief Returns true if this property is static + */ bool getStatic() const { return isStatic; } @@ -116,6 +139,7 @@ namespace wrenbind17 { /** * @ingroup wrenbind17 + * @brief A foreign class */ class ForeignKlass { public: @@ -130,6 +154,9 @@ namespace wrenbind17 { virtual void generate(std::ostream& os) const = 0; + /*! + * @brief Looks up a foreign function that belongs to this class + */ ForeignMethod& findFunc(const std::string& name, const bool isStatic) { const auto it = methods.find(name); if (it == methods.end()) @@ -139,6 +166,9 @@ namespace wrenbind17 { return *it->second; } + /*! + * @brief Looks up a foreign property that belongs to this class + */ ForeignProp& findProp(const std::string& name, const bool isStatic) { const auto it = props.find(name); if (it == props.end()) @@ -148,6 +178,9 @@ namespace wrenbind17 { return *it->second; } + /*! + * @brief Finds a function based on the signature + */ WrenForeignMethodFn findSignature(const std::string& signature, const bool isStatic) { switch (signature[0]) { case '[': @@ -183,10 +216,16 @@ namespace wrenbind17 { } } + /*! + * @brief Returns the name of this foreign class + */ const std::string& getName() const { return name; } + /*! + * @brief Returns a struct with pointers to the allocator and deallocator + */ WrenForeignClassMethods& getAllocators() { return allocators; } @@ -201,6 +240,7 @@ namespace wrenbind17 { /** * @ingroup wrenbind17 + * @brief Type specific implementation of foreign method */ template class ForeignMethodImpl : public ForeignMethod { public: @@ -371,6 +411,7 @@ namespace wrenbind17 { /** * @ingroup wrenbind17 + * @brtief Type specific implementation of foreign class */ template class ForeignKlassImpl : public ForeignKlass { public: @@ -388,6 +429,9 @@ namespace wrenbind17 { // Notice the "auto&" ForeignKlassImpl(const ForeignKlassImpl& other) = delete; + /*! + * @brief Generate Wren code for this class + */ void generate(std::ostream& os) const override { os << "foreign class " << name << " {\n"; if (!ctorDef.empty()) { @@ -402,6 +446,9 @@ namespace wrenbind17 { os << "}\n\n"; } + /*! + * @brief Add a constructor to this class + */ template void ctor(const std::string& name = "new") { allocators.allocate = &detail::ForeignKlassAllocator::allocate; allocators.finalize = &detail::ForeignKlassAllocator::finalize; @@ -534,31 +581,224 @@ namespace wrenbind17 { }; #endif + /*! + * @brief Add a member function to this class + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * void bar() {...} + * int baz() const {...} + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.func<&Foo::bar>("bar"); + * cls.func<&Foo::baz>("baz"); + * } + * @endcode + */ template void func(std::string name) { auto ptr = ForeignMethodDetails::make(std::move(name)); methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a member operator function to this class that exists outside of the class + * @see ForeignMethodOperator + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * Foo& operator+(const std::string& other) {...} + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.func<&Foo::operator+>(wren::ForeignMethodOperator::OPERATOR_ADD); + * } + * @endcode + */ template void func(const ForeignMethodOperator name) { auto ptr = ForeignMethodDetails::make(name); methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a member function via a static function + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * This function does not accept class methods, but instead it uses regular functions + * that have first parameter as "this" which is a reference to the class you are + * adding. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * + * std::string message; + * }; + * + * static std::string fooBaz(Foo& self, int a, int b) { + * return self.message; + * } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.funcExt<&fooBaz>("bar"); + * } + * @endcode + */ template void funcExt(std::string name) { auto ptr = ForeignMethodExtDetails::make(std::move(name)); methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a member operator via a static function that exists outside of the class + * @see ForeignMethodOperator + * @details Same as funcExt but instead it can accept an operator enumeration. + */ template void funcExt(const ForeignMethodOperator name) { auto ptr = ForeignMethodExtDetails::make(name); methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a static function to this class + * @see func + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * This only works if the function you are adding is static. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * static void bar() {...} + * static int baz() const {...} + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.funcStatic<&Foo::bar>("bar"); + * cls.funcStatic<&Foo::baz>("baz"); + * } + * @endcode + */ template void funcStatic(std::string name) { auto ptr = detail::ForeignFunctionDetails::make(std::move(name)); methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a static function to this class that exists outside of the class + * @see funcStatic + * @details The number of arguments and what type of arguments + * this class needs is handled at compile time with metaprogramming. + * When this C++ function you are adding is called from Wren, it will check + * the types passed and will match the C++ signature. If the types do not match + * an exception is thrown that can be handled by Wren as a fiber. + * + * This only works if the function you are adding is static. + * + * Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * }; + * + * static void fooBar() { ... } + * static void fooBaz() { ... } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add some methods + * cls.funcStatic<&fooBar>("bar"); + * cls.funcStatic<&fooBaz>("baz"); + * } + * @endcode + */ template void funcStaticExt(std::string name) { // This is exactly the same as funcStatic because there is // no difference for "static void Foo::foo(){}" and "void foo(){}"! @@ -566,18 +806,81 @@ namespace wrenbind17 { methods.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a read-write variable to this class + * @details Example: + * + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * + * std::string msg; + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read write Wren class property + * cls.var<&Foo::msg>("msg"); + * } + * @endcode + */ template void var(std::string name) { using R = typename detail::GetPointerType::type; auto ptr = ForeignVarDetails::make(std::move(name), false); props.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a read-only variable to this class + * @details This is exactly the same as var() but the variable + * can be only read from Wren. It cannot be reassigned. + */ template void varReadonly(std::string name) { using R = typename detail::GetPointerType::type; auto ptr = ForeignVarReadonlyDetails::make(std::move(name), true); props.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a read-write property to this class via a getter and a setter + * @details This essentially creates the same thing as var() + * but instead of using pointer to the class field, it uses getter and setter functions. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * void setMsg(const std::string& msg) {...} + * const std::string& getMsg() const {...} + * private: + * std::string msg; + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read write Wren class property + * cls.prop<&Foo::getMsg, &Foo::setMsg>("msg"); + * } + * @endcode + */ template void prop(std::string name) { auto g = ForeignGetterDetails::method(); auto s = ForeignSetterDetails::method(); @@ -585,12 +888,81 @@ namespace wrenbind17 { props.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a read-onlu property to this class via a getter + * @details This essentially creates the same thing as varReadonly() + * but instead of using pointer to the class field, it uses a getter. + * This property will be read only and cannot be reassigned from Wren. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * void setMsg(const std::string& msg) {...} + * const std::string& getMsg() const {...} + * private: + * std::string msg; + * }; + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read only Wren class property + * cls.propReadonly<&Foo::getMsg>("msg"); + * } + * @endcode + */ template void propReadonly(std::string name) { auto g = ForeignGetterDetails::method(); auto ptr = std::make_unique(std::move(name), g, nullptr, false); props.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a read-write property to this class via a getter and a setter + * @see prop + * @details This essentially creates the same thing as var() + * but instead of using pointer to the class field, it uses a static getter and setter. + * The setter and getter do not have to belong to the class itself, but must be + * static functions, and must accept the class as a reference as a first parameter. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * std::string msg; + * }; + * + * static void fooSetMsg(Foo& self, const std::string& msg) { + * self.msg = msg; + * } + * + * static const std::string& msg fooGetMsg(Foo& self) { + * return self.msg; + * } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read write Wren class property + * cls.propExt<&fooGetMsg, &fooSetMsg>("msg"); + * } + * @endcode + */ template void propExt(std::string name) { auto g = ForeignGetterExtDetails::method(); auto s = ForeignSetterExtDetails::method(); @@ -598,6 +970,40 @@ namespace wrenbind17 { props.insert(std::make_pair(ptr->getName(), std::move(ptr))); } + /*! + * @brief Add a read-only property to this class via a getter + * @see propReadonly + * @details This essentially creates the same thing as varReadonly() + * but instead of using pointer to the class field, it uses a static getter. + * The setter and does not have to belong to the class itself, but must be + * static function, and must accept the class as a reference as a first parameter. + * @code + * class Foo { + * public: + * Foo(const std::string& msg) {...} + * std::string msg; + * }; + * + * static const std::string& msg fooGetMsg(Foo& self) { + * return self.msg; + * } + * + * int main() { + * ... + * wren::VM vm; + * auto& m = vm.module("mymodule"); + * + * // Add class "Foo" + * auto& cls = m.klass("Foo"); + * + * // Define constructor (you can only specify one constructor) + * cls.ctor(); + * + * // Add class variable as a read only Wren class property + * cls.propReadonlyExt<&fooGetMsg>("msg"); + * } + * @endcode + */ template void propReadonlyExt(std::string name) { auto g = ForeignGetterExtDetails::method(); auto ptr = std::make_unique(std::move(name), g, nullptr, false); diff --git a/include/wrenbind17/handle.hpp b/include/wrenbind17/handle.hpp index 0a09ab01..ebc9c49f 100644 --- a/include/wrenbind17/handle.hpp +++ b/include/wrenbind17/handle.hpp @@ -1,27 +1,30 @@ #pragma once -#include #include +#include /** * @ingroup wrenbind17 */ namespace wrenbind17 { - class Callback; + std::shared_ptr getSharedVm(WrenVM* vm); + /** * @ingroup wrenbind17 + * @brief Holds a reference to some Wren type + * @details This is used by Map, Method, and Variable classes. */ class Handle { public: - Handle() : vm(nullptr), handle(nullptr) { + Handle() : handle(nullptr) { } - Handle(WrenVM* vm, WrenHandle* handle) : vm(vm), handle(handle) { + Handle(const std::shared_ptr vm, WrenHandle* handle) : vm(vm), handle(handle) { } ~Handle() { reset(); } Handle(const Handle& other) = delete; - Handle(Handle&& other) noexcept : vm(nullptr), handle(nullptr) { + Handle(Handle&& other) noexcept : handle(nullptr) { swap(other); } Handle& operator=(const Handle& other) = delete; @@ -41,24 +44,31 @@ namespace wrenbind17 { } WrenVM* getVm() const { + if (const auto ptr = vm.lock()) { + return ptr.get(); + } else { + throw RuntimeError("Invalid handle"); + } + } + + const std::weak_ptr& getVmWeak() const { return vm; } void reset() { - if (vm && handle) { - wrenReleaseHandle(vm, handle); - vm = nullptr; + if (!vm.expired() && handle) { + wrenReleaseHandle(vm.lock().get(), handle); + vm.reset(); handle = nullptr; } } operator bool() const { - return vm && handle; + return !vm.expired() && handle; } - friend Callback; private: - WrenVM* vm; + std::weak_ptr vm; WrenHandle* handle; }; } // namespace wrenbind17 diff --git a/include/wrenbind17/map.hpp b/include/wrenbind17/map.hpp new file mode 100644 index 00000000..2289e9a3 --- /dev/null +++ b/include/wrenbind17/map.hpp @@ -0,0 +1,152 @@ +#pragma once + +#include "method.hpp" +#include "pop.hpp" +#include "push.hpp" + +/** + * @ingroup wrenbind17 + */ +namespace wrenbind17 { + /** + * @ingroup wrenbind17 + * @brief Holds native Wren map + */ + class Map { + public: + Map() { + } + Map(const std::shared_ptr& handle) : handle(handle) { + } + ~Map() { + reset(); + } + + Handle& getHandle() { + return *handle; + } + + const Handle& getHandle() const { + return *handle; + } + + operator bool() const { + return handle.operator bool(); + } + + void reset() { + handle.reset(); + } + + /*! + * @brief Checks if a key exists in this map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + * @warning If using strings, make sure that you use std::string because + * raw C-strings are not allowed. + * @note This function accepts any type you want. Integers, strings, booleans, + * does not matter as long as Wren map supports that key type. + */ + template bool contains(const Key& key) const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 2); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + detail::PushHelper::f(ptr.get(), 1, key); + return wrenGetMapContainsKey(ptr.get(), 0, 1); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Returns a value specified by T from the map by a key + * @throws NotFound if the key does not exist in the map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + * @warning If using strings, make sure that you use std::string because + * raw C-strings are not allowed. + * @note This function accepts any type you want. Integers, strings, booleans, + * does not matter as long as Wren map supports that key type. + */ + template T get(const Key& key) const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 3); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + detail::PushHelper::f(ptr.get(), 1, key); + if (!wrenGetMapContainsKey(ptr.get(), 0, 1)) { + throw NotFound(); + } + wrenGetMapValue(ptr.get(), 0, 1, 2); + return detail::PopHelper::f(ptr.get(), 2); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Erases a key from the map + * @throws NotFound if the key does not exist in the map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + * @returns true if the key has been erased, otherwise false + * @warning If using strings, make sure that you use std::string because + * raw C-strings are not allowed. + * @note This function accepts any type you want. Integers, strings, booleans, + * does not matter as long as Wren map supports that key type. + */ + template bool erase(const Key& key) const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 3); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + detail::PushHelper::f(ptr.get(), 1, key); + wrenRemoveMapValue(ptr.get(), 0, 1, 2); + return !detail::is(ptr.get(), 2); + } else { + throw RuntimeError("Invalid handle"); + } + } + + /*! + * @brief Returns the size of the map + * @throws RuntimeError if this is an invalid map or the Wren VM has terminated + */ + size_t count() const { + if (const auto ptr = handle->getVmWeak().lock()) { + wrenEnsureSlots(ptr.get(), 1); + wrenSetSlotHandle(ptr.get(), 0, handle->getHandle()); + return wrenGetMapCount(ptr.get(), 0); + } else { + throw RuntimeError("Invalid handle"); + } + } + + private: + std::shared_ptr handle; + }; + + template <> inline Map detail::getSlot(WrenVM* vm, const int idx) { + validate(vm, idx); + return Map(std::make_shared(getSharedVm(vm), wrenGetSlotHandle(vm, idx))); + } + + template <> inline bool detail::is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WREN_TYPE_MAP; + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, const Map& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, Map&& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, const Map value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, const Map& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, Map& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } +} // namespace wrenbind17 diff --git a/include/wrenbind17/method.hpp b/include/wrenbind17/method.hpp index b1eef6ec..3a5ac062 100644 --- a/include/wrenbind17/method.hpp +++ b/include/wrenbind17/method.hpp @@ -13,8 +13,8 @@ namespace wrenbind17 { (void)idx; } - template inline void pushArgs( - WrenVM* vm, int idx, First&& first, Other&&... other) { + template + inline void pushArgs(WrenVM* vm, int idx, First&& first, Other&&... other) { PushHelper::f(vm, idx, std::forward(first)); pushArgs(vm, ++idx, std::forward(other)...); } @@ -42,14 +42,10 @@ namespace wrenbind17 { */ class Method { public: - Method() - : vm(nullptr) { - } + Method() = default; - Method(WrenVM* vm, std::shared_ptr variable, std::shared_ptr handle) - : vm(vm), - variable(std::move(variable)), - handle(std::move(handle)) { + Method(std::shared_ptr variable, std::shared_ptr handle) + : variable(std::move(variable)), handle(std::move(handle)) { } ~Method() { @@ -57,22 +53,24 @@ namespace wrenbind17 { } template Any operator()(Args&&... args) { - return detail::CallAndReturn::func(vm, variable->getHandle(), handle->getHandle(), - std::forward(args)...); + if (const auto ptr = handle->getVmWeak().lock().get()) { + return detail::CallAndReturn::func(ptr, variable->getHandle(), handle->getHandle(), + std::forward(args)...); + } else { + throw RuntimeError("Invalid handle"); + } } operator bool() const { - return vm && variable && handle; + return variable && handle; } void reset() { - vm = nullptr; handle.reset(); variable.reset(); } private: - WrenVM* vm; std::shared_ptr variable; std::shared_ptr handle; }; diff --git a/include/wrenbind17/pop.hpp b/include/wrenbind17/pop.hpp index b6e06dcd..9b9d0dc3 100644 --- a/include/wrenbind17/pop.hpp +++ b/include/wrenbind17/pop.hpp @@ -1,9 +1,6 @@ #pragma once #include "object.hpp" -#include -#include -#include namespace wrenbind17 { void getClassType(WrenVM* vm, std::string& module, std::string& name, size_t hash); @@ -212,6 +209,15 @@ namespace wrenbind17 { } }; + template struct PopHelper { + static inline const T& f(WrenVM* vm, int idx) { + static_assert(!std::is_same(), "type can't be std::string"); + static_assert(!std::is_same(), "type can't be std::nullptr_t"); + static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); + return *getSlotForeign(vm, idx).get(); + } + }; + template struct PopHelper> { static inline std::shared_ptr f(WrenVM* vm, int idx) { const auto type = wrenGetSlotType(vm, idx); @@ -234,18 +240,9 @@ namespace wrenbind17 { } }; - template struct PopHelper { - static inline const T& f(WrenVM* vm, int idx) { - static_assert(!std::is_same(), "type can't be std::string"); - static_assert(!std::is_same(), "type can't be std::nullptr_t"); - static_assert(!is_shared_ptr::value, "type can't be shared_ptr"); - return *getSlotForeign(vm, idx).get(); - } - }; - template <> inline Handle getSlot(WrenVM* vm, int idx) { validate(vm, idx); - return Handle(vm, wrenGetSlotHandle(vm, idx)); + return Handle(getSharedVm(vm), wrenGetSlotHandle(vm, idx)); } template <> inline std::string getSlot(WrenVM* vm, int idx) { @@ -351,142 +348,6 @@ namespace wrenbind17 { WRENBIND17_POP_HELPER(unsigned char) WRENBIND17_POP_HELPER(float) WRENBIND17_POP_HELPER(double) - - // ============================================================================================================ - // STD VARIANT - // ============================================================================================================ - template VariantType loopAndFindVariant(WrenVM* vm, int idx) { - throw BadCast("Bad cast when getting variant from Wren"); - } - - template - VariantType loopAndFindVariant(WrenVM* vm, const int idx) { - if (CheckSlot::f(vm, idx)) { - return {PopHelper::f(vm, idx)}; - } - return loopAndFindVariant(vm, idx); - } - - template struct PopHelper> { - static inline std::variant f(WrenVM* vm, const int idx) { - using VariantType = typename std::variant; - return loopAndFindVariant(vm, idx); - } - }; - - template struct PopHelper&> { - static inline std::variant f(WrenVM* vm, const int idx) { - using VariantType = typename std::variant; - return loopAndFindVariant(vm, idx); - } - }; - - template struct PopHelper&> { - static inline std::variant f(WrenVM* vm, const int idx) { - using VariantType = typename std::variant; - return loopAndFindVariant(vm, idx); - } - }; - - // ============================================================================================================ - // STD VECTOR - // ============================================================================================================ - - template Iterable popIterable(WrenVM* vm, const int idx) { - Iterable res; - res.reserve(wrenGetListCount(vm, idx)); - for (size_t i = 0; i < res.capacity(); i++) { - wrenGetListElement(vm, idx, static_cast(i), 0); - res.push_back(PopHelper::f(vm, 0)); - } - return res; - } - - template struct PopHelper&> { - static inline std::vector f(WrenVM* vm, const int idx) { - const auto type = wrenGetSlotType(vm, idx); - if (type == WrenType::WREN_TYPE_FOREIGN) { - return *getSlotForeign>(vm, idx).get(); - } - if (type != WrenType::WREN_TYPE_LIST) - throw BadCast("Bad cast when getting value from Wren expected list"); - - return popIterable>(vm, idx); - } - }; - - template struct PopHelper> { - static inline std::vector f(WrenVM* vm, const int idx) { - const auto type = wrenGetSlotType(vm, idx); - if (type == WrenType::WREN_TYPE_FOREIGN) { - return *getSlotForeign>(vm, idx).get(); - } - if (type != WrenType::WREN_TYPE_LIST) - throw BadCast("Bad cast when getting value from Wren expected list"); - - return popIterable>(vm, idx); - } - }; - - // ============================================================================================================ - // STD LIST - // ============================================================================================================ - - template struct PopHelper&> { - static inline std::list f(WrenVM* vm, const int idx) { - const auto type = wrenGetSlotType(vm, idx); - if (type == WrenType::WREN_TYPE_FOREIGN) { - return *getSlotForeign>(vm, idx).get(); - } - if (type != WrenType::WREN_TYPE_LIST) - throw BadCast("Bad cast when getting value from Wren expected list"); - - return popIterable>(vm, idx); - } - }; - - template struct PopHelper> { - static inline std::list f(WrenVM* vm, const int idx) { - const auto type = wrenGetSlotType(vm, idx); - if (type == WrenType::WREN_TYPE_FOREIGN) { - return *getSlotForeign>(vm, idx).get(); - } - if (type != WrenType::WREN_TYPE_LIST) - throw BadCast("Bad cast when getting value from Wren expected list"); - - return popIterable>(vm, idx); - } - }; - - // ============================================================================================================ - // STD QUEUE - // ============================================================================================================ - - template struct PopHelper&> { - static inline std::queue f(WrenVM* vm, const int idx) { - const auto type = wrenGetSlotType(vm, idx); - if (type == WrenType::WREN_TYPE_FOREIGN) { - return *getSlotForeign>(vm, idx).get(); - } - if (type != WrenType::WREN_TYPE_LIST) - throw BadCast("Bad cast when getting value from Wren expected list"); - - return popIterable>(vm, idx); - } - }; - - template struct PopHelper> { - static inline std::queue f(WrenVM* vm, const int idx) { - const auto type = wrenGetSlotType(vm, idx); - if (type == WrenType::WREN_TYPE_FOREIGN) { - return *getSlotForeign>(vm, idx).get(); - } - if (type != WrenType::WREN_TYPE_LIST) - throw BadCast("Bad cast when getting value from Wren expected list"); - - return popIterable>(vm, idx); - } - }; } // namespace detail #endif } // namespace wrenbind17 diff --git a/include/wrenbind17/push.hpp b/include/wrenbind17/push.hpp index 264e26f6..41bb0ad9 100644 --- a/include/wrenbind17/push.hpp +++ b/include/wrenbind17/push.hpp @@ -1,9 +1,6 @@ #pragma once #include "object.hpp" -#include -#include -#include namespace wrenbind17 { #ifndef DOXYGEN_SHOULD_SKIP_THIS @@ -167,16 +164,36 @@ namespace wrenbind17 { WRENBIND17_PUSH_HELPER(bool, wrenSetSlotBool(vm, idx, value)); WRENBIND17_PUSH_HELPER(std::nullptr_t, wrenSetSlotNull(vm, idx)); - // ============================================================================================================ - // STD STRING - // ============================================================================================================ - template <> struct PushHelper { static inline void f(WrenVM* vm, int idx, const std::string value) { wrenSetSlotString(vm, idx, value.c_str()); } }; + template struct PushHelper { + static inline void f(WrenVM* vm, int idx, const char (&value)[N]) { + wrenSetSlotString(vm, idx, value); + } + }; + + template struct PushHelper { + static inline void f(WrenVM* vm, int idx, char (&value)[N]) { + wrenSetSlotString(vm, idx, value); + } + }; + + template <> struct PushHelper { + static inline void f(WrenVM* vm, int idx, const char*& value) { + wrenSetSlotString(vm, idx, value); + } + }; + + template <> struct PushHelper { + static inline void f(WrenVM* vm, int idx, char*& value) { + wrenSetSlotString(vm, idx, value); + } + }; + template <> struct PushHelper { static inline void f(WrenVM* vm, int idx, const std::string value) { wrenSetSlotString(vm, idx, value.c_str()); @@ -201,10 +218,6 @@ namespace wrenbind17 { } }; - // ============================================================================================================ - // STD SHARED POINTERS - // ============================================================================================================ - template struct PushHelper> { static inline void f(WrenVM* vm, int idx, std::shared_ptr value) { static_assert(!std::is_same(), "type can't be std::string"); @@ -251,52 +264,6 @@ namespace wrenbind17 { } }; - // ============================================================================================================ - // STD VARIANT - // ============================================================================================================ - - template - inline void loopAndPushVariant(WrenVM* vm, int idx, const VariantType& v, size_t i) { - PushHelper::f(vm, idx, nullptr); - } - - template - inline void loopAndPushVariant(WrenVM* vm, int idx, const VariantType& v, size_t i) { - if (v.index() == i) { - PushHelper::f(vm, idx, std::get(v)); - } else { - loopAndPushVariant(vm, idx, v, i + 1); - } - } - - template struct PushHelper> { - inline static void f(WrenVM* vm, int idx, const std::variant& value) { - loopAndPushVariant, Ts...>(vm, idx, value, 0); - } - }; - - template struct PushHelper&> { - inline static void f(WrenVM* vm, int idx, const std::variant& value) { - PushHelper>::f(vm, idx, value); - } - }; - - template struct PushHelper*> { - inline static void f(WrenVM* vm, int idx, const std::variant* value) { - PushHelper>::f(vm, idx, *value); - } - }; - - template struct PushHelper&> { - inline static void f(WrenVM* vm, int idx, const std::variant& value) { - PushHelper>::f(vm, idx, value); - } - }; - - // ============================================================================================================ - // STD VECTOR - // ============================================================================================================ - template inline void loopAndPushIterable(WrenVM* vm, const int idx, Iter begin, Iter end) { using T = typename std::iterator_traits::value_type; wrenSetSlotNewList(vm, idx); @@ -307,103 +274,18 @@ namespace wrenbind17 { } } - template struct PushHelper> { - static inline void f(WrenVM* vm, int idx, std::vector value) { - if (isClassRegistered(vm, typeid(std::vector).hash_code())) { - pushAsMove>(vm, idx, std::move(value)); - } else { - loopAndPushIterable(vm, idx, value.begin(), value.end()); - } - } - }; - - template struct PushHelper*> { - static inline void f(WrenVM* vm, int idx, std::vector* value) { - if (isClassRegistered(vm, typeid(std::vector).hash_code())) { - pushAsPtr>(vm, idx, value); - } else { - loopAndPushIterable(vm, idx, value->begin(), value->end()); - } - } - }; - - template struct PushHelper&> { - static inline void f(WrenVM* vm, int idx, const std::vector& value) { - if (isClassRegistered(vm, typeid(std::vector).hash_code())) { - pushAsConstRef>(vm, idx, value); - } else { - loopAndPushIterable(vm, idx, value.begin(), value.end()); - } - } - }; - - // ============================================================================================================ - // STD List - // ============================================================================================================ - - template struct PushHelper> { - static inline void f(WrenVM* vm, int idx, std::list value) { - if (isClassRegistered(vm, typeid(std::list).hash_code())) { - pushAsMove>(vm, idx, std::move(value)); - } else { - loopAndPushIterable(vm, idx, value.begin(), value.end()); - } - } - }; - - template struct PushHelper*> { - static inline void f(WrenVM* vm, int idx, std::list* value) { - if (isClassRegistered(vm, typeid(std::list).hash_code())) { - pushAsPtr>(vm, idx, value); - } else { - loopAndPushIterable(vm, idx, value->begin(), value->end()); - } - } - }; - - template struct PushHelper&> { - static inline void f(WrenVM* vm, int idx, const std::list& value) { - if (isClassRegistered(vm, typeid(std::list).hash_code())) { - pushAsConstRef>(vm, idx, value); - } else { - loopAndPushIterable(vm, idx, value.begin(), value.end()); - } - } - }; - - // ============================================================================================================ - // STD Queue - // ============================================================================================================ - - template struct PushHelper> { - static inline void f(WrenVM* vm, int idx, std::queue value) { - if (isClassRegistered(vm, typeid(std::queue).hash_code())) { - pushAsMove>(vm, idx, std::move(value)); - } else { - loopAndPushIterable(vm, idx, value.begin(), value.end()); - } - } - }; - - template struct PushHelper*> { - static inline void f(WrenVM* vm, int idx, std::queue* value) { - if (isClassRegistered(vm, typeid(std::queue).hash_code())) { - pushAsPtr>(vm, idx, value); - } else { - loopAndPushIterable(vm, idx, value->begin(), value->end()); - } - } - }; - - template struct PushHelper&> { - static inline void f(WrenVM* vm, int idx, const std::queue& value) { - if (isClassRegistered(vm, typeid(std::queue).hash_code())) { - pushAsConstRef>(vm, idx, value); - } else { - loopAndPushIterable(vm, idx, value.begin(), value.end()); - } + template inline void loopAndPushKeyPair(WrenVM* vm, const int idx, Iter begin, Iter end) { + using T = typename std::iterator_traits::value_type; + using Key = typename T::first_type; + using Value = typename T::second_type; + wrenSetSlotNewMap(vm, idx); + wrenEnsureSlots(vm, 3); + for (auto it = begin; it != end; ++it) { + PushHelper::f(vm, idx + 1, std::forward(it->first)); + PushHelper::f(vm, idx + 2, std::forward(it->second)); + wrenSetMapValue(vm, idx, idx + 1, idx + 2); } - }; + } } // namespace detail #endif } // namespace wrenbind17 diff --git a/include/wrenbind17/std.hpp b/include/wrenbind17/std.hpp index 12bbd04d..d3a94af7 100644 --- a/include/wrenbind17/std.hpp +++ b/include/wrenbind17/std.hpp @@ -7,6 +7,7 @@ #include namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS namespace detail { template struct is_equality_comparable : std::false_type {}; @@ -34,6 +35,7 @@ namespace wrenbind17 { self.end(); } }; +#endif template class StdVectorBindings { public: diff --git a/include/wrenbind17/stddeque.hpp b/include/wrenbind17/stddeque.hpp new file mode 100644 index 00000000..c9069788 --- /dev/null +++ b/include/wrenbind17/stddeque.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "pop.hpp" +#include "push.hpp" +#include + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + namespace detail { + template struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::deque value) { + if (isClassRegistered(vm, typeid(std::deque).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::deque* value) { + if (isClassRegistered(vm, typeid(std::deque).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } + }; + + template struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::deque& value) { + if (isClassRegistered(vm, typeid(std::deque).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PopHelper&> { + static inline std::deque f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) + throw BadCast("Bad cast when getting value from Wren expected list"); + + std::deque res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.push_back(PopHelper::f(vm, idx + 1)); + } + return res; + } + }; + + template struct PopHelper> { + static inline std::deque f(WrenVM* vm, const int idx) { + return PopHelper>::f(vm, idx); + } + }; + } // namespace detail +#endif +} // namespace wrenbind17 diff --git a/include/wrenbind17/stdlist.hpp b/include/wrenbind17/stdlist.hpp new file mode 100644 index 00000000..988ae0b9 --- /dev/null +++ b/include/wrenbind17/stdlist.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "pop.hpp" +#include "push.hpp" +#include + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + namespace detail { + template struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::list value) { + if (isClassRegistered(vm, typeid(std::list).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::list* value) { + if (isClassRegistered(vm, typeid(std::list).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } + }; + + template struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::list& value) { + if (isClassRegistered(vm, typeid(std::list).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PopHelper&> { + static inline std::list f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) + throw BadCast("Bad cast when getting value from Wren expected list"); + + std::list res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.push_back(PopHelper::f(vm, idx + 1)); + } + return res; + } + }; + + template struct PopHelper> { + static inline std::list f(WrenVM* vm, const int idx) { + return PopHelper&>::f(vm, idx); + } + }; + } // namespace detail +#endif +} // namespace wrenbind17 diff --git a/include/wrenbind17/stdmap.hpp b/include/wrenbind17/stdmap.hpp new file mode 100644 index 00000000..ac9eb4c5 --- /dev/null +++ b/include/wrenbind17/stdmap.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "pop.hpp" +#include "push.hpp" +#include +#include + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + namespace detail { + template struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::map value) { + if (isClassRegistered(vm, typeid(std::map).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::map* value) { + if (isClassRegistered(vm, typeid(std::map).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value->begin(), value->end()); + } + } + }; + + template struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::map& value) { + if (isClassRegistered(vm, typeid(std::map).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::unordered_map value) { + if (isClassRegistered(vm, typeid(std::unordered_map).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::unordered_map* value) { + if (isClassRegistered(vm, typeid(std::unordered_map).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value->begin(), value->end()); + } + } + }; + + template struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::unordered_map& value) { + if (isClassRegistered(vm, typeid(std::unordered_map).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushKeyPair(vm, idx, value.begin(), value.end()); + } + } + }; + } // namespace detail +#endif +} // namespace wrenbind17 diff --git a/include/wrenbind17/stdoptional.hpp b/include/wrenbind17/stdoptional.hpp new file mode 100644 index 00000000..32fb7f09 --- /dev/null +++ b/include/wrenbind17/stdoptional.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include "pop.hpp" +#include "push.hpp" +#include + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + namespace detail { + template struct PushHelper> { + inline static void f(WrenVM* vm, int idx, const std::optional& value) { + if (value.has_value()) { + PushHelper::f(vm, idx, value.value()); + } else { + PushHelper::f(vm, idx, nullptr); + } + } + }; + + template struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::optional& value) { + PushHelper>::f(vm, idx, value); + } + }; + + template struct PushHelper*> { + inline static void f(WrenVM* vm, int idx, const std::optional* value) { + PushHelper>::f(vm, idx, *value); + } + }; + + template struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::optional& value) { + PushHelper>::f(vm, idx, value); + } + }; + + template struct PopHelper> { + static inline std::optional f(WrenVM* vm, const int idx) { + if (is(vm, idx)) { + return std::nullopt; + } else { + return PopHelper::f(vm, idx); + } + } + }; + + template struct PopHelper&> { + static inline std::optional f(WrenVM* vm, const int idx) { + return PopHelper>::f(vm, idx); + } + }; + } // namespace detail +#endif +} // namespace wrenbind17 diff --git a/include/wrenbind17/stdset.hpp b/include/wrenbind17/stdset.hpp new file mode 100644 index 00000000..b518ef07 --- /dev/null +++ b/include/wrenbind17/stdset.hpp @@ -0,0 +1,126 @@ +#pragma once + +#include "pop.hpp" +#include "push.hpp" +#include +#include + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + namespace detail { + template struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::set value) { + if (isClassRegistered(vm, typeid(std::set).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::set* value) { + if (isClassRegistered(vm, typeid(std::set).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } + }; + + template struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::set& value) { + if (isClassRegistered(vm, typeid(std::set).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PopHelper&> { + static inline std::set f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) + throw BadCast("Bad cast when getting value from Wren expected list"); + + std::set res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + res.reserve(size); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.insert(PopHelper::f(vm, idx + 1)); + } + return res; + } + }; + + template struct PopHelper> { + static inline std::set f(WrenVM* vm, const int idx) { + return PopHelper&>::f(vm, idx); + } + }; + + template struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::unordered_set value) { + if (isClassRegistered(vm, typeid(std::unordered_set).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::unordered_set* value) { + if (isClassRegistered(vm, typeid(std::unordered_set).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } + }; + + template struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::unordered_set& value) { + if (isClassRegistered(vm, typeid(std::unordered_set).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PopHelper&> { + static inline std::set f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) + throw BadCast("Bad cast when getting value from Wren expected list"); + + std::unordered_set res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + res.reserve(size); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.insert(PopHelper::f(vm, idx + 1)); + } + return res; + } + }; + + template struct PopHelper> { + static inline std::set f(WrenVM* vm, const int idx) { + return PopHelper&>::f(vm, idx); + } + }; + } // namespace detail +#endif +} // namespace wrenbind17 diff --git a/include/wrenbind17/stdvariant.hpp b/include/wrenbind17/stdvariant.hpp new file mode 100644 index 00000000..ebbd5daf --- /dev/null +++ b/include/wrenbind17/stdvariant.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "pop.hpp" +#include "push.hpp" +#include + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + namespace detail { + template + inline void loopAndPushVariant(WrenVM* vm, int idx, const VariantType& v, size_t i) { + PushHelper::f(vm, idx, nullptr); + } + + template + inline void loopAndPushVariant(WrenVM* vm, int idx, const VariantType& v, size_t i) { + if (v.index() == i) { + PushHelper::f(vm, idx, std::get(v)); + } else { + loopAndPushVariant(vm, idx, v, i + 1); + } + } + + template struct PushHelper> { + inline static void f(WrenVM* vm, int idx, const std::variant& value) { + loopAndPushVariant, Ts...>(vm, idx, value, 0); + } + }; + + template struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::variant& value) { + PushHelper>::f(vm, idx, value); + } + }; + + template struct PushHelper*> { + inline static void f(WrenVM* vm, int idx, const std::variant* value) { + PushHelper>::f(vm, idx, *value); + } + }; + + template struct PushHelper&> { + inline static void f(WrenVM* vm, int idx, const std::variant& value) { + PushHelper>::f(vm, idx, value); + } + }; + + template VariantType loopAndFindVariant(WrenVM* vm, int idx) { + throw BadCast("Bad cast when getting variant from Wren"); + } + + template + VariantType loopAndFindVariant(WrenVM* vm, const int idx) { + if (CheckSlot::f(vm, idx)) { + return {PopHelper::f(vm, idx)}; + } + return loopAndFindVariant(vm, idx); + } + + template struct PopHelper> { + static inline std::variant f(WrenVM* vm, const int idx) { + using VariantType = typename std::variant; + return loopAndFindVariant(vm, idx); + } + }; + + template struct PopHelper&> { + static inline std::variant f(WrenVM* vm, const int idx) { + using VariantType = typename std::variant; + return loopAndFindVariant(vm, idx); + } + }; + } // namespace detail +#endif +} // namespace wrenbind17 diff --git a/include/wrenbind17/stdvector.hpp b/include/wrenbind17/stdvector.hpp new file mode 100644 index 00000000..c1a79b62 --- /dev/null +++ b/include/wrenbind17/stdvector.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "pop.hpp" +#include "push.hpp" +#include + +namespace wrenbind17 { +#ifndef DOXYGEN_SHOULD_SKIP_THIS + namespace detail { + template struct PushHelper> { + static inline void f(WrenVM* vm, int idx, std::vector value) { + if (isClassRegistered(vm, typeid(std::vector).hash_code())) { + pushAsMove>(vm, idx, std::move(value)); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PushHelper*> { + static inline void f(WrenVM* vm, int idx, std::vector* value) { + if (isClassRegistered(vm, typeid(std::vector).hash_code())) { + pushAsPtr>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value->begin(), value->end()); + } + } + }; + + template struct PushHelper&> { + static inline void f(WrenVM* vm, int idx, const std::vector& value) { + if (isClassRegistered(vm, typeid(std::vector).hash_code())) { + pushAsConstRef>(vm, idx, value); + } else { + loopAndPushIterable(vm, idx, value.begin(), value.end()); + } + } + }; + + template struct PopHelper&> { + static inline std::vector f(WrenVM* vm, const int idx) { + const auto type = wrenGetSlotType(vm, idx); + if (type == WrenType::WREN_TYPE_FOREIGN) { + return *getSlotForeign>(vm, idx).get(); + } + if (type != WrenType::WREN_TYPE_LIST) + throw BadCast("Bad cast when getting value from Wren expected list"); + + std::vector res; + const auto size = wrenGetListCount(vm, idx); + wrenEnsureSlots(vm, 1); + res.reserve(size); + for (size_t i = 0; i < size; i++) { + wrenGetListElement(vm, idx, static_cast(i), idx + 1); + res.push_back(PopHelper::f(vm, idx + 1)); + } + return res; + } + }; + + template struct PopHelper> { + static inline std::vector f(WrenVM* vm, const int idx) { + return PopHelper&>::f(vm, idx); + } + }; + } // namespace detail +#endif +} // namespace wrenbind17 diff --git a/include/wrenbind17/variable.hpp b/include/wrenbind17/variable.hpp index d55ac40a..ba229cbb 100644 --- a/include/wrenbind17/variable.hpp +++ b/include/wrenbind17/variable.hpp @@ -1,8 +1,8 @@ #pragma once #include "method.hpp" -#include "push.hpp" #include "pop.hpp" +#include "push.hpp" /** * @ingroup wrenbind17 @@ -10,43 +10,90 @@ namespace wrenbind17 { /** * @ingroup wrenbind17 + * @brief Holds some Wren variable which can be a class or class instance + * @details You can use this to pass around Wren classes or class instances. + * You can also use this to get Wren class methods. You can also call a Wren + * function from C++ side and pass this Variable into Wren. To get this variable, + * either call a Wren function that returns some class (or class instance), or + * use wrenbind17::VM::find() function that looks up a class (or class instance) + * based on the module name. + * @note This variable can safely outlive the wrenbind17::VM class. If that happens + * then functions of this class will throw wrenbind17::RuntimeError exception. This + * holder will not try to free the Wren variable if the VM has been terminated. You + * don't have to worry about the lifetime of this holder. (uses weak pointers). */ class Variable { public: - Variable() : vm(nullptr) { + Variable() { } - Variable(WrenVM* vm, const std::shared_ptr& handle) : vm(vm), handle(handle) { + Variable(const std::shared_ptr& handle) : handle(handle) { } ~Variable() { reset(); } + /*! + * @brief Looks up a function from this Wren variable. + * @details The signature must match Wren function signature. + * For example: `main()` or `foo(_,_)` etc. Use underscores to specify + * parameters of that function. + * @throws RuntimeError if this variable is invalid or the Wren VM has terminated. + */ Method func(const std::string& signature) { - auto* h = wrenMakeCallHandle(vm, signature.c_str()); - return Method(vm, handle, std::make_shared(vm, h)); + if (const auto ptr = handle->getVmWeak().lock()) { + auto* h = wrenMakeCallHandle(ptr.get(), signature.c_str()); + return Method(handle, std::make_shared(ptr, h)); + } else { + throw RuntimeError("Invalid handle"); + } + } + + Handle& getHandle() { + return *handle; } - WrenHandle* getHandle() const { - return handle->getHandle(); + const Handle& getHandle() const { + return *handle; } operator bool() const { - return vm && handle; + return handle.operator bool(); } void reset() { - vm = nullptr; handle.reset(); } private: - WrenVM* vm; std::shared_ptr handle; }; - template <> - inline Variable detail::getSlot(WrenVM* vm, int idx) { + template <> inline Variable detail::getSlot(WrenVM* vm, const int idx) { validate(vm, idx); - return Variable(vm, std::make_shared(vm, wrenGetSlotHandle(vm, idx))); + return Variable(std::make_shared(getSharedVm(vm), wrenGetSlotHandle(vm, idx))); + } + + template <> inline bool detail::is(WrenVM* vm, const int idx) { + return wrenGetSlotType(vm, idx) == WREN_TYPE_UNKNOWN; + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, const Variable& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, Variable&& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, const Variable value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, const Variable& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); + } + + template <> inline void detail::PushHelper::f(WrenVM* vm, int idx, Variable& value) { + wrenSetSlotHandle(value.getHandle().getVm(), idx, value.getHandle().getHandle()); } } // namespace wrenbind17 diff --git a/include/wrenbind17/vm.hpp b/include/wrenbind17/vm.hpp index 640a874e..c6ca1541 100644 --- a/include/wrenbind17/vm.hpp +++ b/include/wrenbind17/vm.hpp @@ -1,7 +1,7 @@ #pragma once +#include "map.hpp" #include "module.hpp" -#include "std.hpp" #include "variable.hpp" #include #include @@ -37,15 +37,24 @@ namespace wrenbind17 { /** * @ingroup wrenbind17 + * @brief Holds the entire Wren VM from which all of the magic happens */ class VM { public: + /*! + * @brief The only constructor available + * @param paths The lookup paths used by the import loader function + * @param initHeap The size of the heap at the beginning + * @param minHeap The minimum size of the heap + * @param heapGrowth How the heap should grow + */ inline explicit VM(std::vector paths = {"./"}, const size_t initHeap = 1024 * 1024, const size_t minHeap = 1024 * 1024 * 10, const int heapGrowth = 50) - : vm(nullptr), paths(std::move(paths)) { + : data(std::make_unique()) { - printFn = [](const char* text) -> void { std::cout << text; }; - loadFileFn = [](const std::vector& paths, const std::string& name) -> std::string { + data->paths = std::move(paths); + data->printFn = [](const char* text) -> void { std::cout << text; }; + data->loadFileFn = [](const std::vector& paths, const std::string& name) -> std::string { for (const auto& path : paths) { const auto test = path + "/" + std::string(name) + ".wren"; @@ -60,14 +69,14 @@ namespace wrenbind17 { throw NotFound(); }; - wrenInitConfiguration(&config); - config.initialHeapSize = initHeap; - config.minHeapSize = minHeap; - config.heapGrowthPercent = heapGrowth; - config.userData = this; - config.reallocateFn = std::realloc; - config.loadModuleFn = [](WrenVM* vm, const char* name) -> char* { - auto& self = *reinterpret_cast(wrenGetUserData(vm)); + wrenInitConfiguration(&data->config); + data->config.initialHeapSize = initHeap; + data->config.minHeapSize = minHeap; + data->config.heapGrowthPercent = heapGrowth; + data->config.userData = data.get(); + data->config.reallocateFn = std::realloc; + data->config.loadModuleFn = [](WrenVM* vm, const char* name) -> char* { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); const auto mod = self.modules.find(name); if (mod != self.modules.end()) { @@ -87,9 +96,9 @@ namespace wrenbind17 { return nullptr; } }; - config.bindForeignMethodFn = [](WrenVM* vm, const char* module, const char* className, const bool isStatic, - const char* signature) -> WrenForeignMethodFn { - auto& self = *reinterpret_cast(wrenGetUserData(vm)); + data->config.bindForeignMethodFn = [](WrenVM* vm, const char* module, const char* className, + const bool isStatic, const char* signature) -> WrenForeignMethodFn { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); try { auto& found = self.modules.at(module); auto& klass = found.findKlass(className); @@ -100,9 +109,9 @@ namespace wrenbind17 { return nullptr; } }; - config.bindForeignClassFn = [](WrenVM* vm, const char* module, - const char* className) -> WrenForeignClassMethods { - auto& self = *reinterpret_cast(wrenGetUserData(vm)); + data->config.bindForeignClassFn = [](WrenVM* vm, const char* module, + const char* className) -> WrenForeignClassMethods { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); try { auto& found = self.modules.at(module); auto& klass = found.findKlass(className); @@ -112,13 +121,13 @@ namespace wrenbind17 { return WrenForeignClassMethods{nullptr, nullptr}; } }; - config.writeFn = [](WrenVM* vm, const char* text) { - auto& self = *reinterpret_cast(wrenGetUserData(vm)); + data->config.writeFn = [](WrenVM* vm, const char* text) { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); self.printFn(text); }; - config.errorFn = [](WrenVM* vm, WrenErrorType type, const char* module, const int line, - const char* message) { - auto& self = *reinterpret_cast(wrenGetUserData(vm)); + data->config.errorFn = [](WrenVM* vm, WrenErrorType type, const char* module, const int line, + const char* message) { + auto& self = *reinterpret_cast(wrenGetUserData(vm)); std::stringstream ss; switch (type) { case WREN_ERROR_COMPILE: @@ -141,20 +150,16 @@ namespace wrenbind17 { self.lastError += ss.str(); }; - vm = wrenNewVM(&config); + data->vm = std::shared_ptr(wrenNewVM(&data->config), [](WrenVM* ptr) { wrenFreeVM(ptr); }); } inline VM(const VM& other) = delete; - inline VM(VM&& other) noexcept : vm(nullptr) { + inline VM(VM&& other) noexcept { swap(other); } - inline ~VM() { - if (vm) { - wrenFreeVM(vm); - } - } + inline ~VM() = default; inline VM& operator=(const VM& other) = delete; @@ -166,27 +171,32 @@ namespace wrenbind17 { } inline void swap(VM& other) noexcept { - std::swap(vm, other.vm); - std::swap(config, other.config); - std::swap(paths, other.paths); - std::swap(modules, other.modules); - std::swap(classToModule, other.classToModule); - std::swap(classToName, other.classToName); - std::swap(classCasting, other.classCasting); - std::swap(lastError, other.lastError); - std::swap(nextError, other.nextError); - std::swap(printFn, other.printFn); - std::swap(loadFileFn, other.loadFileFn); + std::swap(data, other.data); } + /*! + * @brief Runs a Wren source code by passing it as a string + * @param name The module name to assign this code into, this module name + * can be then used to import this code in some other place + * @param code Your raw multiline Wren code + * @throws CompileError if the compilation has failed + */ inline void runFromSource(const std::string& name, const std::string& code) { - const auto result = wrenInterpret(vm, name.c_str(), code.c_str()); + const auto result = wrenInterpret(data->vm.get(), name.c_str(), code.c_str()); if (result != WREN_RESULT_SUCCESS) { throw CompileError(getLastError()); } return; } + /*! + * @brief Runs a Wren source code directly from a file + * @param name The module name to assign this code into, this module name + * can be then used to import this code in some other place + * @param path The path to the file + * @throws Exception if the file has not been found or the file cannot be read + * @throws CompileError if the compilation has failed + */ inline void runFromFile(const std::string& name, const std::string& path) { std::ifstream t(path); if (!t) @@ -195,118 +205,197 @@ namespace wrenbind17 { runFromSource(name, str); } + /*! + * @brief Runs a Wren source code by passing it as a string + * @see setLoadFileFunc + * @param name The module name to load, this will use the loader function to + * load the file from + * @throws CompileError if the compilation has failed + */ inline void runFromModule(const std::string& name) { - const auto source = loadFileFn(paths, name); + const auto source = data->loadFileFn(data->paths, name); runFromSource(name, source); } + /*! + * @brief Looks up a variable from a module + * @param module The name of the module to look for the variable in + * @param name The name of the variable or a class itself that must start + * with a capital letter + * @throws NotFound if the variable has not been found + */ inline Variable find(const std::string& module, const std::string& name) { - wrenEnsureSlots(vm, 1); - wrenGetVariable(vm, module.c_str(), name.c_str(), 0); - auto* handle = wrenGetSlotHandle(vm, 0); + wrenEnsureSlots(data->vm.get(), 1); + wrenGetVariable(data->vm.get(), module.c_str(), name.c_str(), 0); + auto* handle = wrenGetSlotHandle(data->vm.get(), 0); if (!handle) throw NotFound(); - return Variable(vm, std::make_shared(vm, handle)); + return Variable(std::make_shared(data->vm, handle)); } + /*! + * @brief Creates a new custom module + * @note Calling this function multiple times with the same name + * does not create a new module, but instead it returns the same module. + */ inline ForeignModule& module(const std::string& name) { - auto it = modules.find(name); - if (it == modules.end()) { - it = modules.insert(std::make_pair(name, ForeignModule(name, vm))).first; + auto it = data->modules.find(name); + if (it == data->modules.end()) { + it = data->modules.insert(std::make_pair(name, ForeignModule(name, data->vm.get()))).first; } return it->second; } inline void addClassType(const std::string& module, const std::string& name, const size_t hash) { - classToModule.insert(std::make_pair(hash, module)); - classToName.insert(std::make_pair(hash, name)); + data->addClassType(module, name, hash); } inline void getClassType(std::string& module, std::string& name, const size_t hash) { - module = classToModule.at(hash); - name = classToName.at(hash); + data->getClassType(module, name, hash); } inline bool isClassRegistered(const size_t hash) const { - return classToModule.find(hash) != classToModule.end(); + return data->isClassRegistered(hash); } inline void addClassCast(std::shared_ptr convertor, const size_t hash, const size_t other) { - classCasting.insert(std::make_pair(std::make_pair(hash, other), std::move(convertor))); + data->addClassCast(std::move(convertor), hash, other); } inline detail::ForeignPtrConvertor* getClassCast(const size_t hash, const size_t other) { - return classCasting.at(std::pair(hash, other)).get(); + return data->getClassCast(hash, other); } inline std::string getLastError() { - std::string str; - std::swap(str, lastError); - return str; + return data->getLastError(); } inline void setNextError(std::string str) { - nextError = std::move(str); + data->setNextError(std::move(str)); } + /*! + * @brief Set a custom print function that is used by the System.print() + * @see PrintFn + */ inline void setPrintFunc(const PrintFn& fn) { - printFn = fn; + data->printFn = fn; } + /*! + * @brief Set a custom loader function for imports + * @see LoadFileFn + * @details This must be a function that accepts a std::vector of strings + * (which are the lookup paths from the constructor) and the name of the import + * as the second parameter. You must return the source code from this custom function. + * If you want to cancel the import, simply throw an exception. + */ inline void setLoadFileFunc(const LoadFileFn& fn) { - loadFileFn = fn; - } - - inline WrenVM* getVm() const { - return vm; + data->loadFileFn = fn; } + /*! + * @brief Runs the garbage collector + */ inline void gc() { - wrenCollectGarbage(vm); + wrenCollectGarbage(data->vm.get()); } + class Data { + public: + std::shared_ptr vm; + WrenConfiguration config; + std::vector paths; + std::unordered_map modules; + std::unordered_map classToModule; + std::unordered_map classToName; + std::unordered_map, std::shared_ptr> classCasting; + std::string lastError; + std::string nextError; + PrintFn printFn; + LoadFileFn loadFileFn; + + inline void addClassType(const std::string& module, const std::string& name, const size_t hash) { + classToModule.insert(std::make_pair(hash, module)); + classToName.insert(std::make_pair(hash, name)); + } + + inline void getClassType(std::string& module, std::string& name, const size_t hash) { + module = classToModule.at(hash); + name = classToName.at(hash); + } + + inline bool isClassRegistered(const size_t hash) const { + return classToModule.find(hash) != classToModule.end(); + } + + inline void addClassCast(std::shared_ptr convertor, const size_t hash, + const size_t other) { + classCasting.insert(std::make_pair(std::make_pair(hash, other), std::move(convertor))); + } + + inline detail::ForeignPtrConvertor* getClassCast(const size_t hash, const size_t other) { + return classCasting.at(std::pair(hash, other)).get(); + } + + inline std::string getLastError() { + std::string str; + std::swap(str, lastError); + return str; + } + + inline void setNextError(std::string str) { + nextError = std::move(str); + } + }; + private: - WrenVM* vm; - WrenConfiguration config; - std::vector paths; - std::unordered_map modules; - std::unordered_map classToModule; - std::unordered_map classToName; - std::unordered_map, std::shared_ptr> classCasting; - std::string lastError; - std::string nextError; - PrintFn printFn; - LoadFileFn loadFileFn; + std::unique_ptr data; }; +#ifndef DOXYGEN_SHOULD_SKIP_THIS + inline std::shared_ptr getSharedVm(WrenVM* vm) { + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(self->vm); + return self->vm; + } inline void addClassType(WrenVM* vm, const std::string& module, const std::string& name, const size_t hash) { - auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); self->addClassType(module, name, hash); } inline void getClassType(WrenVM* vm, std::string& module, std::string& name, const size_t hash) { - auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); self->getClassType(module, name, hash); } inline bool isClassRegistered(WrenVM* vm, const size_t hash) { - auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); return self->isClassRegistered(hash); } inline void addClassCast(WrenVM* vm, std::shared_ptr convertor, const size_t hash, const size_t other) { - auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); self->addClassCast(std::move(convertor), hash, other); } inline detail::ForeignPtrConvertor* getClassCast(WrenVM* vm, const size_t hash, const size_t other) { - auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); return self->getClassCast(hash, other); } inline std::string getLastError(WrenVM* vm) { - auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); return self->getLastError(); } inline void setNextError(WrenVM* vm, std::string str) { - auto self = reinterpret_cast(wrenGetUserData(vm)); + assert(vm); + auto self = reinterpret_cast(wrenGetUserData(vm)); self->setNextError(std::move(str)); } +#endif } // namespace wrenbind17 diff --git a/include/wrenbind17/wrenbind17.hpp b/include/wrenbind17/wrenbind17.hpp index bec3f793..c18503a5 100644 --- a/include/wrenbind17/wrenbind17.hpp +++ b/include/wrenbind17/wrenbind17.hpp @@ -5,4 +5,12 @@ * @brief Wren lang binding library for C++17 */ +#include "std.hpp" +#include "stddeque.hpp" +#include "stdlist.hpp" +#include "stdmap.hpp" +#include "stdoptional.hpp" +#include "stdset.hpp" +#include "stdvariant.hpp" +#include "stdvector.hpp" #include "vm.hpp" diff --git a/libs/wren b/libs/wren index 6ab4abe9..58611240 160000 --- a/libs/wren +++ b/libs/wren @@ -1 +1 @@ -Subproject commit 6ab4abe9e3a2767ced01589e9a3d583c840f54c3 +Subproject commit 58611240e75522df1a17ba29d8fd2109b4d2f657 diff --git a/modules/FindCatch2.cmake b/modules/FindCatch2.cmake new file mode 100644 index 00000000..0f200b20 --- /dev/null +++ b/modules/FindCatch2.cmake @@ -0,0 +1,10 @@ +include(FindPackageHandleStandardArgs) + +if(NOT TARGET Catch2) + find_path(CATCH2_INCLUDE_DIR NAMES catch2/catch.hpp PATHS ${CMAKE_CURRENT_LIST_DIR}/../libs/Catch2/single_include) + mark_as_advanced(FORCE CATCH2_INCLUDE_DIR) + add_library(Catch2 INTERFACE IMPORTED) + set_target_properties(Catch2 PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${CATCH2_INCLUDE_DIR}) +endif() + +find_package_handle_standard_args(Catch2 DEFAULT_MSG CATCH2_INCLUDE_DIR) diff --git a/modules/FindWren.cmake b/modules/FindWren.cmake new file mode 100644 index 00000000..4f9d2e73 --- /dev/null +++ b/modules/FindWren.cmake @@ -0,0 +1,14 @@ +include(FindPackageHandleStandardArgs) + +if(NOT TARGET Wren) + find_path(WREN_INCLUDE_DIR NAMES wren.hpp PATHS ${CMAKE_CURRENT_LIST_DIR}/../libs/wren/src/include) + mark_as_advanced(FORCE WREN_INCLUDE_DIR) + + file(GLOB_RECURSE WREN_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/libs/wren/src/vm/*.c) + add_library(Wren STATIC ${WREN_SOURCES}) + target_compile_definitions(Wren PRIVATE WREN_OPT_META=0 WREN_OPT_RANDOM=0) + target_include_directories(Wren PUBLIC ${WREN_INCLUDE_DIR}) + set_target_properties(Wren PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${WREN_INCLUDE_DIR}) +endif() + +find_package_handle_standard_args(Wren DEFAULT_MSG WREN_INCLUDE_DIR) diff --git a/tests/lists.cpp b/tests/lists.cpp index 6cb60e0a..c56a702a 100644 --- a/tests/lists.cpp +++ b/tests/lists.cpp @@ -255,39 +255,44 @@ TEST_CASE("Non comparable list") { wren::StdVectorBindings::bind(m, "NonComparable"); } -TEST_CASE("Native lists") { - typedef std::variant ListItem; +typedef std::variant ListItem; - class NativeListAcceptor { - public: - void set(const std::vector& items) { - this->items = items; - } +template class NativeListAcceptor { +public: + NativeListAcceptor() = default; - void set2(std::vector items) { - this->items = items; - } + void set(const Container& items) { + this->items = items; + } - const std::vector& get() const { - return items; - } + void set2(Container items) { + this->items = items; + } - std::vector get2() const { - return items; - } + const Container& get() const { + return items; + } - private: - std::vector items; - }; + Container get2() const { + return items; + } + +private: + Container items; +}; + +TEST_CASE("Native lists of vector") { + using Container = std::vector; + using ListAcceptor = NativeListAcceptor; wren::VM vm; auto& m = vm.module("test"); - auto& c = m.klass("NativeListAcceptor"); - c.ctor<>(); - c.func<&NativeListAcceptor::set>("set"); - c.func<&NativeListAcceptor::get>("get"); - c.func<&NativeListAcceptor::set2>("set2"); - c.func<&NativeListAcceptor::get2>("get2"); + auto& c = m.klass("NativeListAcceptor"); + c.template ctor<>(); + c.template func<&ListAcceptor::set>("set"); + c.template func<&ListAcceptor::get>("get"); + c.template func<&ListAcceptor::set2>("set2"); + c.template func<&ListAcceptor::get2>("get2"); SECTION("Get element from native array") { const std::string code = R"( @@ -303,8 +308,8 @@ TEST_CASE("Native lists") { } )"; - NativeListAcceptor instance; - std::vector items; + ListAcceptor instance; + Container items; items.push_back(nullptr); items.push_back(std::string("Hello World")); instance.set(items); @@ -330,8 +335,8 @@ TEST_CASE("Native lists") { } )"; - NativeListAcceptor instance; - std::vector items; + ListAcceptor instance; + Container items; items.push_back(nullptr); items.push_back(std::string("Hello World")); instance.set(items); @@ -355,13 +360,17 @@ TEST_CASE("Native lists") { } )"; - NativeListAcceptor instance; + ListAcceptor instance; vm.runFromSource("main", code); auto func = vm.find("main", "Main").func("main(_)"); (void)func(&instance); REQUIRE(instance.get().size() == 4); + REQUIRE(std::get(instance.get()[0]) == nullptr); + REQUIRE(std::get(instance.get()[1]) == 123.0); + REQUIRE(std::get(instance.get()[2]) == true); + REQUIRE(std::get(instance.get()[3]) == "Hello World"); } SECTION("Set elements into native array pass by copy") { @@ -376,12 +385,43 @@ TEST_CASE("Native lists") { } )"; - NativeListAcceptor instance; + ListAcceptor instance; vm.runFromSource("main", code); auto func = vm.find("main", "Main").func("main(_)"); (void)func(&instance); REQUIRE(instance.get().size() == 4); + REQUIRE(std::get(instance.get()[0]) == nullptr); + REQUIRE(std::get(instance.get()[1]) == 123.0); + REQUIRE(std::get(instance.get()[2]) == true); + REQUIRE(std::get(instance.get()[3]) == "Hello World"); } } + +TEST_CASE("Get native lists as vector") { + using Container = std::vector; + + const std::string code = R"( + class Main { + static main() { + return [null, 123, true, "Hello World"] + } + } + )"; + + wren::VM vm; + + vm.runFromSource("main", code); + auto func = vm.find("main", "Main").func("main()"); + + auto res = func(); + REQUIRE(res.isList()); + auto vec = res.as(); + + REQUIRE(vec.size() == 4); + REQUIRE(std::get(vec[0]) == nullptr); + REQUIRE(std::get(vec[1]) == 123.0); + REQUIRE(std::get(vec[2]) == true); + REQUIRE(std::get(vec[3]) == "Hello World"); +} diff --git a/tests/main.cpp b/tests/main.cpp index 95cc1867..6f1e73b6 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -6,17 +6,16 @@ namespace wren = wrenbind17; class RandomClass { public: - RandomClass(std::string msg):msg(msg){ + RandomClass(std::string msg) : msg(msg) { + } - } + ~RandomClass() = default; - ~RandomClass() = default; - - std::string msg; + std::string msg; }; TEST_CASE("Move VM") { - const std::string code = R"( + const std::string code = R"( import "test" for RandomClass class Main { @@ -33,12 +32,12 @@ TEST_CASE("Move VM") { cls.ctor(); cls.var<&RandomClass::msg>("msg"); - vm.runFromSource("main", code); + vm.runFromSource("main", code); - auto vm2 = std::move(vm); + auto vm2 = std::move(vm); - auto main = vm2.find("main", "Main").func("main()"); - auto res = main(); - REQUIRE(res.is()); - REQUIRE(res.as() == "Hello World"); + auto main = vm2.find("main", "Main").func("main()"); + auto res = main(); + REQUIRE(res.is()); + REQUIRE(res.as() == "Hello World"); } diff --git a/tests/maps.cpp b/tests/maps.cpp index 6686b90f..d59245a8 100644 --- a/tests/maps.cpp +++ b/tests/maps.cpp @@ -285,3 +285,179 @@ TEST_CASE("Pass std map to Wren with variant") { REQUIRE(std::get<3>(map.at("eighth")) == nullptr); } } + +TEST_CASE("Pass std map to Wren as native") { + const std::string code = R"( + class Main { + static main(map, key) { + return map[key] + } + } + )"; + + wren::VM vm; + + vm.runFromSource("main", code); + auto func = vm.find("main", "Main").func("main(_,_)"); + + typedef std::variant Multivalue; + std::map map; + map["first"] = 42; + map["second"] = true; + map["third"] = std::string("Hello World"); + map["fourth"] = nullptr; + + auto res = func(map, std::string("first")); + REQUIRE(res.is()); + REQUIRE(res.as() == 42); + + res = func(map, std::string("second")); + REQUIRE(res.is()); + REQUIRE(res.as() == true); + + res = func(map, std::string("third")); + REQUIRE(res.is()); + REQUIRE(res.as() == std::string("Hello World")); + + res = func(map, std::string("fourth")); + REQUIRE(res.is()); +} + +TEST_CASE("Pass std unordered map to Wren as native") { + const std::string code = R"( + class Main { + static main(map, key) { + return map[key] + } + } + )"; + + wren::VM vm; + + vm.runFromSource("main", code); + auto func = vm.find("main", "Main").func("main(_,_)"); + + typedef std::variant Multivalue; + std::unordered_map map; + map["first"] = 42; + map["second"] = true; + map["third"] = std::string("Hello World"); + map["fourth"] = nullptr; + + auto res = func(map, std::string("first")); + REQUIRE(res.is()); + REQUIRE(res.as() == 42); + + res = func(map, std::string("second")); + REQUIRE(res.is()); + REQUIRE(res.as() == true); + + res = func(map, std::string("third")); + REQUIRE(res.is()); + REQUIRE(res.as() == std::string("Hello World")); + + res = func(map, std::string("fourth")); + REQUIRE(res.is()); +} + +TEST_CASE("Get map to Wren as native") { + const std::string code = R"( + class Main { + static main() { + return { + "first": 42, + "second": true, + "third": "Hello World", + "fourth": null + } + } + static other(map) { + return map["third"] + } + } + )"; + + wren::VM vm; + + vm.runFromSource("main", code); + auto func = vm.find("main", "Main").func("main()"); + + auto res = func(); + REQUIRE(res.is()); + auto map = res.as(); + + REQUIRE(map.count() == 4); + + REQUIRE(map.contains(std::string("first")) == true); + REQUIRE(map.contains(std::string("second")) == true); + REQUIRE(map.contains(std::string("third")) == true); + REQUIRE(map.contains(std::string("fourth")) == true); + REQUIRE(map.contains(std::string("fifth")) == false); + + REQUIRE(map.get(std::string("first")) == 42); + REQUIRE(map.get(std::string("second")) == true); + REQUIRE(map.get(std::string("third")) == std::string("Hello World")); + REQUIRE(map.get(std::string("fourth")) == nullptr); + + REQUIRE(map.erase(std::string("first")) == true); + REQUIRE(map.erase(std::string("fifth")) == false); + REQUIRE(map.count() == 3); + + REQUIRE_THROWS(map.get(std::string("first"))); + + auto other = vm.find("main", "Main").func("other(_)"); + res = other(map); + + REQUIRE(res.is()); +} + +TEST_CASE("Get map of ints to Wren as native") { + const std::string code = R"( + class Main { + static main() { + return { + 0: 42, + 1: true, + 2: "Hello World", + 3: null + } + } + static other(map) { + return map[2] + } + } + )"; + + wren::VM vm; + + vm.runFromSource("main", code); + auto func = vm.find("main", "Main").func("main()"); + + auto res = func(); + REQUIRE(res.is()); + auto map = res.as(); + + REQUIRE(map.count() == 4); + + REQUIRE(map.contains(0) == true); + REQUIRE(map.contains(1) == true); + REQUIRE(map.contains(2) == true); + REQUIRE(map.contains(3) == true); + REQUIRE(map.contains(4) == false); + + REQUIRE(map.get(0) == 42); + REQUIRE(map.get(1) == true); + REQUIRE(map.get(2) == std::string("Hello World")); + REQUIRE(map.get(3) == nullptr); + + REQUIRE(map.erase(0) == true); + REQUIRE(map.erase(4) == false); + REQUIRE(map.count() == 3); + + REQUIRE_THROWS(map.get(1)); + + auto other = vm.find("main", "Main").func("other(_)"); + res = other(map); + + REQUIRE(res.is()); +} diff --git a/tests/optional.cpp b/tests/optional.cpp new file mode 100644 index 00000000..d84a0551 --- /dev/null +++ b/tests/optional.cpp @@ -0,0 +1,146 @@ +#include +#include + +namespace wren = wrenbind17; + +TEST_CASE("Pass empty optional into Wren") { + const std::string code = R"( + class Main { + static main(opt) { + return opt == null + } + } + )"; + + wren::VM vm; + + vm.runFromSource("main", code); + auto res = vm.find("main", "Main").func("main(_)")(std::optional{std::nullopt}); + + REQUIRE(res.is()); + REQUIRE(res.as() == true); +} + +TEST_CASE("Pass optional into Wren") { + const std::string code = R"( + class Main { + static main(opt) { + return opt == 123 + } + } + )"; + + wren::VM vm; + + vm.runFromSource("main", code); + auto res = vm.find("main", "Main").func("main(_)")(std::optional{123}); + + REQUIRE(res.is()); + REQUIRE(res.as() == true); +} + +class OptionalAcceptor { +public: + void set(const std::optional& opt) { + this->opt = opt; + } + + std::optional opt; +}; + +TEST_CASE("Pass empty optional into C++") { + const std::string code = R"( + import "test" + + class Main { + static main(acceptor) { + acceptor.set(null) + } + } + )"; + + wren::VM vm; + + auto& m = vm.module("test"); + auto& cls = m.klass("OptionalAcceptor"); + cls.func<&OptionalAcceptor::set>("set"); + + OptionalAcceptor instance; + + vm.runFromSource("main", code); + auto res = vm.find("main", "Main").func("main(_)")(&instance); + + REQUIRE(!instance.opt.has_value()); +} + +TEST_CASE("Pass optional into C++") { + const std::string code = R"( + import "test" + + class Main { + static main(acceptor) { + acceptor.set(123) + } + } + )"; + + wren::VM vm; + + auto& m = vm.module("test"); + auto& cls = m.klass("OptionalAcceptor"); + cls.func<&OptionalAcceptor::set>("set"); + + OptionalAcceptor instance; + + vm.runFromSource("main", code); + auto res = vm.find("main", "Main").func("main(_)")(&instance); + + REQUIRE(instance.opt.has_value()); + REQUIRE(instance.opt.value() == 123); +} + +class OptionalFoo { +public: + OptionalFoo(std::string msg) : msg(std::move(msg)) { + } + + std::string msg; +}; + +class OptionalFooAcceptor { +public: + void set(const std::optional& opt) { + this->opt = opt; + } + + std::optional opt; +}; + +TEST_CASE("Pass optional of class into C++") { + const std::string code = R"( + import "test" for OptionalFoo + + class Main { + static main(acceptor) { + acceptor.set(OptionalFoo.new("Hello World!" )) + } + } + )"; + + wren::VM vm; + + auto& m = vm.module("test"); + auto& cls = m.klass("OptionalAcceptor"); + cls.func<&OptionalFooAcceptor::set>("set"); + + auto& cls2 = m.klass("OptionalFoo"); + cls2.ctor(); + + OptionalFooAcceptor instance; + + vm.runFromSource("main", code); + auto res = vm.find("main", "Main").func("main(_)")(&instance); + + REQUIRE(instance.opt.has_value()); + REQUIRE(instance.opt.value().msg == "Hello World!"); +} diff --git a/tests/slots.cpp b/tests/slots.cpp index fd8e48b5..017db731 100644 --- a/tests/slots.cpp +++ b/tests/slots.cpp @@ -25,70 +25,70 @@ TEST_CASE("Set slot and return by calling Wren") { vm.runFromSource("main", code); auto baz = vm.find("main", "Foo").func("baz(_)"); - SECTION("char"){ + SECTION("char") { sendAndCheck(baz, 42); } - SECTION("short"){ + SECTION("short") { sendAndCheck(baz, 42); } - SECTION("int"){ + SECTION("int") { sendAndCheck(baz, 42); } - SECTION("long"){ + SECTION("long") { sendAndCheck(baz, 42); } - SECTION("long long"){ + SECTION("long long") { sendAndCheck(baz, 42); } - SECTION("unsigned char"){ + SECTION("unsigned char") { sendAndCheck(baz, 42); } - SECTION("unsigned short"){ + SECTION("unsigned short") { sendAndCheck(baz, 42); } - SECTION("unsigned int"){ + SECTION("unsigned int") { sendAndCheck(baz, 42); } - SECTION("unsigned long"){ + SECTION("unsigned long") { sendAndCheck(baz, 42); } - SECTION("unsigned long long>"){ + SECTION("unsigned long long>") { sendAndCheck(baz, 42); } - SECTION("unsigned"){ + SECTION("unsigned") { sendAndCheck(baz, 42); } - SECTION("float"){ + SECTION("float") { sendAndCheck(baz, 42.0f); } - SECTION("double"){ + SECTION("double") { sendAndCheck(baz, 42.0); } - SECTION("bool"){ + SECTION("bool") { sendAndCheck(baz, true); } - SECTION("int8_t"){ + SECTION("int8_t") { sendAndCheck(baz, 42); } - SECTION("int16_t"){ + SECTION("int16_t") { sendAndCheck(baz, 42); } - SECTION("int32_t"){ + SECTION("int32_t") { sendAndCheck(baz, 42); } - SECTION("int64_t"){ + SECTION("int64_t") { sendAndCheck(baz, 42); } - SECTION("uint32_t"){ + SECTION("uint32_t") { sendAndCheck(baz, 42); } - SECTION("uint64_t"){ + SECTION("uint64_t") { sendAndCheck(baz, 42); } - SECTION("string"){ + SECTION("string") { sendAndCheck(baz, std::string("Hello World")); } - SECTION("nullptr_t"){ + SECTION("nullptr_t") { sendAndCheck(baz, nullptr); } } @@ -146,58 +146,58 @@ void testCaseGetSlotByCallingCpp(const std::string& initStr, T init, const std:: } TEST_CASE("Get slot by calling C++") { - SECTION("char"){ + SECTION("char") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("short"){ + SECTION("short") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("int"){ + SECTION("int") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("long"){ + SECTION("long") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("long long"){ + SECTION("long long") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("unsigned int"){ + SECTION("unsigned int") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("unsigned long"){ + SECTION("unsigned long") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("unsigned long long"){ + SECTION("unsigned long long") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("bool"){ + SECTION("bool") { testCaseGetSlotByCallingCpp("false", false, "true", true); } - SECTION("string"){ + SECTION("string") { testCaseGetSlotByCallingCpp("\"Hello\"", "Hello", "\"World\"", "World"); } - SECTION("float"){ + SECTION("float") { testCaseGetSlotByCallingCpp("42.1", 42.1f, "123.3", 123.3f); } - SECTION("double"){ + SECTION("double") { testCaseGetSlotByCallingCpp("42.1", 42.1, "123.3", 123.3); } - SECTION("int8_t"){ + SECTION("int8_t") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("int16_t"){ + SECTION("int16_t") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("int32_t"){ + SECTION("int32_t") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("int64_t"){ + SECTION("int64_t") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("uint32_t"){ + SECTION("uint32_t") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } - SECTION("uint64_t"){ + SECTION("uint64_t") { testCaseGetSlotByCallingCpp("42", 42, "123", 123); } } diff --git a/tests/strings.cpp b/tests/strings.cpp new file mode 100644 index 00000000..eff76a15 --- /dev/null +++ b/tests/strings.cpp @@ -0,0 +1,41 @@ +#include +#include + +namespace wren = wrenbind17; + +TEST_CASE("Char strings as array") { + const std::string code = R"( + class Main { + static main(arg) { + return arg + } + } + )"; + + wren::VM vm; + vm.runFromSource("main", code); + + auto main = vm.find("main", "Main").func("main(_)"); + auto res = main("Hello World!"); + REQUIRE(res.is()); + REQUIRE(res.as() == "Hello World!"); +} + +TEST_CASE("Char strings as pointer") { + const std::string code = R"( + class Main { + static main(arg) { + return arg + } + } + )"; + + wren::VM vm; + vm.runFromSource("main", code); + + auto main = vm.find("main", "Main").func("main(_)"); + const char* ptr = "Hello World!"; + auto res = main(ptr); + REQUIRE(res.is()); + REQUIRE(res.as() == "Hello World!"); +}