A C++ wrapper for the wren scripting language (wren.io)
Find a file
2022-05-17 02:22:14 +02:00
examples Foreign function callbacks now receive a ModuleAndName parameter too. 2022-05-17 01:06:36 +02:00
include Build fix for function that is not being used by any examples yet 2022-05-17 02:22:14 +02:00
src Fix for aarch64 2022-05-17 01:33:51 +02:00
subprojects/wren Update wren to latest and fix build 2022-04-22 22:02:59 +02:00
test Extend test program a bit to try the new reset() method. 2021-02-12 14:43:25 +01:00
.gitignore Start working on a c++ interface. 2020-04-25 20:45:59 +02:00
.gitmodules Sample code that prints a message from wren 2020-04-25 01:49:46 +02:00
COPYING Attach licence 2020-04-30 23:33:54 +02:00
meson.build Version bump to 0.1.3 2022-05-15 23:09:49 +02:00
meson_options.txt Add build option wren_with_name_guessing 2022-05-15 20:14:34 +02:00
README.md Update readme, still incomplete 2022-05-15 23:09:36 +02:00

Wrenpp

Wrenpp is a lightweight C++ wrapper for the official Wren C library. It aims to package all the features accessible through the C API in a modern C++ interface that is easy to use, while keeping the overhead to a minimum.

Current state

The library is functional and provides access to most of the features of Wren. Some are better tested than others. There are no known memory leaks. Please keep in mind this is an early version and more testing and work will be needed.

Philosophy

The overall principle behind this wrapper is to be feature rich while also being lightweight and allowing fine-grained control for users who need it.

By design, wrenpp hides the Wren C API entirely, but all functions (considered "low level" relative to this wrapper) are still accessible through the VM class. If the function you need is not available there it's probably just missing and adding it should be a trivial task.

Wrenpp provides you with convenience functions in vm_fun.hpp that are built on top of the VM class and make basic use of Wren simpler. You're still free to "mix & match" higher level functions with lower level ones, and you can decide to not use portions of the wrapper entirely.

Building

Wrenpp

You can make a release build like this:

mkdir build
cd build
meson setup -Dbuildtype=release ..
ninja

The build scripts understand the following options (double check in meson_options.txt):

  1. build_testing set to true to compile Wren's unit tests too
  2. build_examples set to true to include examples from the examples/ directory in the build
  3. wren_with_cli set to false to exclude the command line tool from the Wren subproject build
  4. wren_with_rand set to true to compile Wren with random support
  5. wren_with_meta set to true to compile Wren with meta support
  6. wrenpp_with_name_guessing enable class name guessing in c++ code. It currently affects only example code and header files, not the library itself. It controls the WRENPP_WITH_NAME_GUESSING preprocessor symbol. When it's defined, you will have access to better error reporting and extra functions, such as a make_wren_object() overload that doesn't require you to specify a string class name.

Note that some example projects require random support in Wren, without which they will crash. If you want to run examples or you are getting "Address boundary error" segfaults recompile Wrenpp after enabling the example and/or meta options:

mkdir build
cd build
meson setup -Dbuildtype=debug -Dbuild_examples=true -Dwren_with_rand=true -Dwren_with_meta=true ..
ninja

Wren only

If you only want to compile the C library of Wren, of course you can do that:

mkdir build
cd build
meson setup -Dbuildtype=release -Dwren_with_rand=true -Dwren_with_meta=true ../subprojects/wren
ninja

This will build Wren as a shared object. If you want a static library instead you can add the -Ddefault_library=static parameter to your Meson command, or -Ddefault_library=both if you want tboth the shared and the static version of Wren.

Usage

Meson

You can use Wrenpp as a Meson subproject like you would do normally. For example:

wrenpp = subproject('wrenpp', default_options: ['wren_with_rand=true'])
wrenpp_dep = wrenpp.get_variable('wrenpp_dep')

executable('my_project',
  'main.cpp',
  dependencies: wrenpp_dep,
)

This will make Wrenpp build as part of your project with Wren's optional random module enabled.

Alternatively you can use the subproject as a fallback if there is no Wrenpp installed in your system already:

wrenpp_dep = dependency('wrenpp', version: '>=0.1.0',
  fallback: ['wrenpp', 'wrenpp_dep'],
  default_options: ['wren_with_rand=true'],
)

Note that when you use Wrenpp as a subproject, Wrenpp's subproject Wren will become a sub-subproject of your project. This is how Meson works and it simply means that in your top level source directory you will have to run this command before you will be able to compile (Meson should detect and print this as well):

meson wrap promote subprojects/wrenpp/subprojects/wren

C++

For working examples refer to the source files in the examples/ directory. "Greet" is the simplest example, while "math_vector" is showing off more features.

Header files

In order to use Wrenpp in your project you will need to include wrenpp/vm.hpp at least. A description of all the relevant header files follows:

  • vm.hpp The VM class provides simple wrappers around the C API functions. It wraps and hides the WrenVM pointer and provides RAII around it. Available methods aim to be as close to their C counterparts as possible. You can use objects of this class in a way that mirrors very closely the way you would use the C API.
  • vm_fun.hpp This header provides functions on top of the VM class. Their goal is to make common operations easier and more compact in your code.
  • def_configuration.hpp The DefConfiguration struct extends the bare Configuration struct by providing an implementation for write_fn, error_fn and reallocate_fn. They are implemented in terms of std::cout, std::cerr and new[]/delete[] respectively. You can safely ignore this header if this is not suitable for your project.

Other header files are intended for Wrenpp's use and you shouldn't need to use them. You are free to use them in your project if you think the code inside is useful to you, however keep in mind that those are not providing core Wrenpp functionalities and may be removed or modified in the future.

Initialisation

Wrenpp aims to make your code as clear and concise as possible. To instantiate a VM (which wraps WrenVM) and run a simple script you can do the following:

#include <wrenpp/vm.hpp>
#include <wrenpp/def_configuration.hpp>

int main() {
	wren::DefConfiguration config;
	wren::VM vm(&config, nullptr);

	vm.interpret("main", "System.print(\"Hello world!\")");
	return 0;
}

Foreign functions

You have two options to register foreign functions with wrenpp: the easiest one is to just use the callback manager available through the VM object:

#include <wrenpp/vm.hpp>
#include <wrenpp/def_configuration.hpp>
#include <wrenpp/callback_manager.hpp> //necessary if you want to use the callback manager

int main() {
	wren::DefConfiguration config;
	wren::VM vm(&config, nullptr);

	vm.callback_manager()
		.add_callback(true, "your_module", "YourClass", "your_method(_,_)", &your_method);

	vm.interpret("main", your_fun_script_here);
	return 0;
}

The current implementation available in DefConfiguration registers a callback to Wren that looks up a std::unordered_map for the correct c++ function and then forwards the call to it. This should be good enough for most cases, however you are free to provide your own callback for wren by writing your own Configuration class. Wrenpp tries to not impose you any particular helper code, in fact if you don't wish to use the CallbackManager returned by vm.callback_manager() then you don't even need to include the relative header file! There will still be a std::unordered_map in memory (because that's owned by vm) but it will never be used.

The second method requires you to provide the full Wren foreign functions callback:

#include <wrenpp/vm.hpp>
#include <wrenpp/def_configuration.hpp>

class MyConf : public wren::DefConfiguration {
public:
	//This is the callback that Wren will call, refer to the C API documentation
	//for more details.
	wren::foreign_method_t foreign_method_fn(
		wren::VM* vm,
		std::string_view module,
		std::string_view class_name,
		bool is_static,
		std::string_view signature
	) {
		if (module == "your_module" and class_name == "YourClass") {
			if (is_static and signature == "your_method(_,_)")
				return &your_method;
		}
		return nullptr;
	}
}

int main() {
	MyConf config;
	wren::VM vm(&config, nullptr);

	vm.interpret("main", your_fun_script_here);
	return 0;
}

The above code is very close to what you'd do with the C API. If you need, you can even pass a user data pointer to the VM constructor (nullptr in this snippet) and retrieve it later from within foreign_method_fn() via vm.void_user_data() or vm.user_data(). You're unlikely to need it as, as you can see, foreign_method_fn() is not static and so you can have some state stored in your MyConf object, however you still get the chance to use the C-like user data mechanism if you wish to.

Note that for the above to work the foreign_method_fn() method in MyConf must be named exactly like that, and the signature must match (except for the static and const keywords). Ideally the code above should alse be noexcept as there will be C code in the call stack when that method is invoked.

Implementation

Work in progress