Mercurial > malikania
changeset 11:fe95a8db2970
Common: add libjs, #463
While here, change includes to add full destination when building only.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 01 Apr 2016 13:43:30 +0200 |
parents | cff1d99eff11 |
children | 0c62e0af6af7 |
files | libcommon/CMakeLists.txt libcommon/malikania/js.h |
diffstat | 2 files changed, 2388 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/libcommon/CMakeLists.txt Fri Apr 01 13:41:21 2016 +0200 +++ b/libcommon/CMakeLists.txt Fri Apr 01 13:43:30 2016 +0200 @@ -23,6 +23,7 @@ ${CMAKE_CURRENT_SOURCE_DIR}/malikania/elapsed-timer.h ${CMAKE_CURRENT_SOURCE_DIR}/malikania/game.h ${CMAKE_CURRENT_SOURCE_DIR}/malikania/id.h + ${CMAKE_CURRENT_SOURCE_DIR}/malikania/js.h ${CMAKE_CURRENT_SOURCE_DIR}/malikania/json.h ${CMAKE_CURRENT_SOURCE_DIR}/malikania/resources-loader.h ${CMAKE_CURRENT_SOURCE_DIR}/malikania/resources-locator.h @@ -57,7 +58,10 @@ TARGET libcommon SOURCES ${HEADERS} ${SOURCES} FLAGS "MALIKANIA_COMMON_BUILD" - PUBLIC_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR} ${INCLUDES} + PUBLIC_INCLUDES + ${CMAKE_CURRENT_SOURCE_DIR} + ${INCLUDES} + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/malikania> LIBRARIES extern-jansson ${LIBRARIES} )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/malikania/js.h Fri Apr 01 13:43:30 2016 +0200 @@ -0,0 +1,2383 @@ +/* + * js.h -- JavaScript C++14 wrapper for Duktape + * + * Copyright (c) 2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef MALIKANIA_JS_H +#define MALIKANIA_JS_H + +/** + * @file js.h + * @brief Bring JavaScript using Duktape. + * + * This file provides usual Duktape function renamed and placed into `duk` namespace. It also replaces error + * code with exceptions when possible. + * + * For convenience, this file also provides templated functions, overloads and much more. + */ + +#include <cassert> +#include <functional> +#include <memory> +#include <string> +#include <type_traits> +#include <unordered_map> +#include <utility> +#include <vector> + +#include <duktape.h> + +namespace malikania { + +/** + * Duktape C++ namespace wrapper. + */ +namespace duk { + +class Context; + +using CodePoint = duk_codepoint_t; +using ContextPtr = duk_context *; +using Index = duk_idx_t; +using Ret = duk_ret_t; +using Size = duk_size_t; + +/** + * @class StackAssert + * @brief Stack sanity checker. + * + * Instanciate this class where you need to manipulate the Duktape stack outside a Duktape/C function, its destructor + * will examinate if the stack size matches the user expected size. + * + * When compiled with NDEBUG, this class does nothing. + * + * To use it, just declare an lvalue at the beginning of your function. + */ +class StackAssert { +#if !defined(NDEBUG) +private: + ContextPtr m_context; + unsigned m_expected; + unsigned m_begin; +#endif + +public: + /** + * Create the stack checker. + * + * No-op if NDEBUG is set. + * + * @param ctx the context + * @param expected the size expected relative to the already existing values + */ + inline StackAssert(ContextPtr ctx, unsigned expected = 0) noexcept +#if !defined(NDEBUG) + : m_context(ctx) + , m_expected(expected) + , m_begin(static_cast<unsigned>(duk_get_top(ctx))) +#endif + { +#if defined(NDEBUG) + (void)ctx; + (void)expected; +#endif + } + + /** + * Verify the expected size. + * + * No-op if NDEBUG is set. + */ + inline ~StackAssert() noexcept + { +#if !defined(NDEBUG) + assert((unsigned)duk_get_top(m_context) - m_begin == m_expected); +#endif + } +}; + +/** + * @class Object + * @brief Empty class tag for push() function. + */ +class Object { +}; + +/** + * @class Array + * @brief Empty class tag for push() function. + */ +class Array { +}; + +/** + * @class Global + * @brief Empty class tag to push the global object. + */ +class Global { +}; + +/** + * @class Undefined + * @brief Empty class tag to push undefined to the stack. + */ +class Undefined { +}; + +/** + * @class Null + * @brief Empty class tag to push null to the stack. + */ +class Null { +}; + +/** + * @class This + * @brief Empty class tag to push this binding to the stack. + */ +class This { +}; + +/** + * @class RawPointer + * @brief Push a non-managed pointer to Duktape, the pointer will never be deleted. + * @note For a managed pointer with prototype, see Pointer + */ +template <typename T> +class RawPointer { +public: + /** + * The pointer to push. + */ + T *object; +}; + +/** + * @brief Manage shared_ptr from C++ and JavaScript + * + * This class allowed you to push and retrieve shared_ptr from C++ and JavaScript without taking care of ownership + * and deletion. + * + */ +template <typename T> +class Shared { +public: + /** + * The shared object. + */ + std::shared_ptr<T> object; +}; + +/** + * @brief Manage pointers from C++ and JavaScript + * + * This class allowed you to push and retrieve C++ pointers from C++ and JavaScript. The object will be deleted when + * the JavaScript garbage collectors collect them so never store a pointer created with this. + * + * The only requirement is to have the function `void prototype(Context &ctx)` in your class T. + */ +template <typename T> +class Pointer { +public: + /** + * The object. + */ + T *object{nullptr}; +}; + +/** + * @class Function + * @brief Duktape/C function definition. + * + * This class wraps the std::function as a Duktape/C function by storing a copied pointer. + */ +class Function { +public: + /** + * The function pointer, must not be null. + */ + duk_c_function function; + + /** + * Number of args that the function takes + */ + duk_idx_t nargs{0}; +}; + +/** + * Map of functions to set on an object. + */ +using FunctionMap = std::unordered_map<std::string, Function>; + +/** + * Map of string to type, ideal for setting constants like enums. + */ +template <typename Type> +using Map = std::unordered_map<std::string, Type>; + +/** + * @class ErrorInfo + * @brief Error description. + * + * This class fills the fields got in an Error object. + */ +class ErrorInfo : public std::exception { +public: + std::string name; //!< name of error + std::string message; //!< error message + std::string stack; //!< stack if available + std::string fileName; //!< filename if applicable + int lineNumber{0}; //!< line number if applicable + + /** + * Get the error message. This effectively returns message field. + * + * @return the message + */ + const char *what() const noexcept override + { + return message.c_str(); + } +}; + +/** + * @class TypeTraits + * @brief Type information to implement new types in JavaScript's context. + * + * This class depending on your needs may have the following functions: + * + * - `static void construct(Context &ctx, Type value)` + * - `static Type get(Context &ctx, int index)` + * - `static bool is(Context &ctx, int index)` + * - `static Type optional(Context &ctx, int index, Type defaultValue)` + * - `static void push(Context &ctx, Type value)` + * - `static Type require(Context &ctx, int index)` + * + * The `construct` function is used in Context::construct to build a new value as this (e.g. constructors). + * + * The `get` function is used in Context::get, Context::getProperty, Context::getGlobal to retrieve a value from the + * stack. + * + * The `is` function is used in Context::is to check if the value on the stack is of type `Type`. + * + * The `optional` function is used in Context::optional to get a value or a replacement if not applicable. + * + * The `push` function is used in Context::push to usually create a new value on the stack but some specializations + * may not (e.g. FunctionMap). + * + * The `require` function is used in Context::require to get a value from the stack or raise a JavaScript exception if + * not applicable. + * + * This class is fully specialized for: `bool`, `const char *`, `double`, `int`, `std::string`. + * + * It is also partially specialized for : `Global`, `Object`, `Array`, `Undefined`, `Null`, `std::vector<Type>`. + */ +template <typename Type> +class TypeTraits { +}; + +/** + * @class Context + * @brief RAII based Duktape handler. + * + * This class is implicitly convertible to duk_context for convenience. + */ +class Context { +private: + using Deleter = void (*)(duk_context *); + using Handle = std::unique_ptr<duk_context, Deleter>; + + Handle m_handle; + + Context(const Context &) = delete; + Context &operator=(const Context &) = delete; + Context(const Context &&) = delete; + Context &operator=(const Context &&) = delete; + +public: + /** + * Create default context. + */ + inline Context() + : m_handle(duk_create_heap_default(), duk_destroy_heap) + { + } + + /** + * Convert the context to the native Duktape/C type. + * + * @return the duk_context + */ + inline operator duk_context *() noexcept + { + return m_handle.get(); + } + + /** + * Convert the context to the native Duktape/C type. + * + * @return the duk_context + */ + inline operator duk_context *() const noexcept + { + return m_handle.get(); + } +}; + +/** + * Get the error object when a JavaScript error has been thrown (e.g. eval failure). + * + * @param ctx the context + * @param index the index + * @return the information + */ +inline ErrorInfo error(ContextPtr ctx, int index) +{ + ErrorInfo error; + + index = duk_normalize_index(ctx, index); + + duk_get_prop_string(ctx, index, "name"); + error.name = duk_to_string(ctx, -1); + duk_get_prop_string(ctx, index, "message"); + error.message = duk_to_string(ctx, -1); + duk_get_prop_string(ctx, index, "fileName"); + error.fileName = duk_to_string(ctx, -1); + duk_get_prop_string(ctx, index, "lineNumber"); + error.lineNumber = duk_to_int(ctx, -1); + duk_get_prop_string(ctx, index, "stack"); + error.stack = duk_to_string(ctx, -1); + duk_pop_n(ctx, 5); + + return error; +} + +/** + * Wrapper for [duk_base64_decode](http://duktape.org/api.html#duk_base64_decode). + * + * @param ctx the context + * @param index the index + */ +inline void base64Decode(ContextPtr ctx, Index index) +{ + duk_base64_decode(ctx, index); +} + +/** + * Wrapper for [duk_base64_encode](http://duktape.org/api.html#duk_base64_encode). + * + * @param ctx the context + * @param index the index + */ +inline void base64Encode(ContextPtr ctx, Index index) +{ + duk_base64_encode(ctx, index); +} + +/** + * Wrapper for [duk_call](http://duktape.org/api.html#duk_call). + * + * @param ctx the context + * @param nargs the number of arguments + */ +inline void call(ContextPtr ctx, Index nargs = 0) +{ + duk_call(ctx, nargs); +} + +/** + * Wrapper for [duk_call_method](http://duktape.org/api.html#duk_call_method). + * + * @param ctx the context + * @param nargs the number of arguments + */ +inline void callMethod(ContextPtr ctx, Index nargs = 0) +{ + duk_call_method(ctx, nargs); +} + +/** + * Wrapper for [duk_call_prop](http://duktape.org/api.html#duk_call_prop). + * + * @param ctx the context + * @param index the object index + * @param nargs the number of arguments + */ +inline void callProperty(ContextPtr ctx, Index index, Index nargs = 0) +{ + duk_call_prop(ctx, index, nargs); +} + +/** + * Wrapper for [duk_char_code_at](http://duktape.org/api.html#duk_char_code_at). + * + * @param ctx the context + * @param index the index + * @param charOffset the offset + */ +inline CodePoint charCodeAt(ContextPtr ctx, Index index, Size charOffset) +{ + return duk_char_code_at(ctx, index, charOffset); +} + +/** + * Wrapper for [duk_check_stack](http://duktape.org/api.html#duk_check_stack). + * + * @param ctx the context + * @param extra the extra space + * @return true if space is available + */ +inline bool checkStack(ContextPtr ctx, Index extra) +{ + return duk_check_stack(ctx, extra); +} + +/** + * Wrapper for [duk_check_stack_top](http://duktape.org/api.html#duk_check_stack_top). + * + * @param ctx the context + * @param top the extra space + * @return true if space is available + */ +inline bool checkStackTop(ContextPtr ctx, Index top) +{ + return duk_check_stack_top(ctx, top); +} + +/** + * Wrapper for [duk_check_type](http://duktape.org/api.html#duk_check_type). + * + * @param ctx the context + * @param index the value index + * @param type the desired type + * @return true if object is given type + */ +inline bool checkType(ContextPtr ctx, Index index, int type) +{ + return duk_check_type(ctx, index, type); +} + +/** + * Wrapper for [duk_check_type_mask](http://duktape.org/api.html#duk_check_type_mask). + * + * @param ctx the context + * @param index the value index + * @param mask the desired mask + * @return true if object is one of the type + */ +inline bool checkTypeMask(ContextPtr ctx, Index index, unsigned mask) +{ + return duk_check_type_mask(ctx, index, mask); +} + +/** + * Wrapper for [duk_compact](http://duktape.org/api.html#duk_compact). + * + * @param ctx the context + * @param objIndex the object index + */ +inline void compact(ContextPtr ctx, Index objIndex) +{ + duk_compact(ctx, objIndex); +} + +/** + * Wrapper for [duk_concat](http://duktape.org/api.html#duk_concat). + * + * @param ctx the context + * @param count the number of values + */ +inline void concat(ContextPtr ctx, Index count) +{ + duk_concat(ctx, count); +} + +/** + * Wrapper for [duk_copy](http://duktape.org/api.html#duk_copy). + * + * @param from the from index + * @param to the destination + */ +inline void copy(ContextPtr ctx, Index from, Index to) +{ + duk_copy(ctx, from, to); +} + +/** + * Wrapper for [duk_def_prop](http://duktape.org/api.html#duk_def_prop). + * + * @param index the object index + * @param flags the flags + */ +inline void defineProperty(ContextPtr ctx, Index index, unsigned flags) +{ + duk_def_prop(ctx, index, flags); +} + +/** + * Wrapper for [duk_del_prop](http://duktape.org/api.html#duk_del_prop). + * + * @param index the object index + * @return true if deleted + */ +inline bool deleteProperty(ContextPtr ctx, Index index) +{ + return duk_del_prop(ctx, index); +} + +/** + * Wrapper for [duk_del_prop](http://duktape.org/api.html#duk_del_prop). + * + * @param index the object index + * @param position the property index + * @return true if deleted + */ +inline bool deleteProperty(ContextPtr ctx, Index index, unsigned position) +{ + return duk_del_prop_index(ctx, index, position); +} + +/** + * Wrapper for [duk_del_prop](http://duktape.org/api.html#duk_del_prop). + * + * @param index the object index + * @param name the property name + * @return true if deleted + */ +inline bool deleteProperty(ContextPtr ctx, Index index, const std::string &name) +{ + return duk_del_prop_string(ctx, index, name.c_str()); +} + +/** + * Wrapper for [duk_dup](http://duktape.org/api.html#duk_dup). + * + * @param index the value to copy + */ +inline void dup(ContextPtr ctx, int index = -1) +{ + duk_dup(ctx, index); +} + +/** + * Wrapper for [duk_equals](http://duktape.org/api.html#duk_equals). + * + * @param ctx the context + * @param index1 the first value + * @param index2 the second value + * @return true if they equal + */ +inline bool equals(ContextPtr ctx, Index index1, Index index2) +{ + return duk_equals(ctx, index1, index2); +} + +/** + * Wrapper for [duk_eval](http://duktape.org/api.html#duk_eval). + * + * @param ctx the context + */ +inline void eval(ContextPtr ctx) +{ + duk_eval(ctx); +} + +/** + * Wrapper for [duk_eval_file](http://duktape.org/api.html#duk_eval_file). + * + * @param ctx the context + * @param path the path + * @param result true to get the result at the top of the stack + */ +inline void evalFile(ContextPtr ctx, const std::string &path, bool result = true) +{ + if (result) + duk_eval_file(ctx, path.c_str()); + else + duk_eval_file_noresult(ctx, path.c_str()); +} + +/** + * Wrapper for [duk_eval_string](http://duktape.org/api.html#duk_eval_string). + * + * @param ctx the context + * @param src the source script + * @param result true to get the result at the top of the stack + */ +inline void evalString(ContextPtr ctx, const std::string &src, bool result = true) +{ + if (result) + duk_eval_string(ctx, src.c_str()); + else + duk_eval_string_noresult(ctx, src.c_str()); +} +/** + * Wrapper for [duk_gc](http://duktape.org/api.html#duk_gc). + * + * @param ctx the context + * @param flags the flags + */ +inline void gc(ContextPtr ctx, unsigned flags = 0) +{ + duk_gc(ctx, flags); +} + +/** + * Wrapper for [duk_has_prop](http://duktape.org/api.html#duk_has_prop). + * + * @param ctx the context + * @param index the object index + * @return true if has + */ +inline bool hasProperty(ContextPtr ctx, Index index) +{ + return duk_has_prop(ctx, index); +} + +/** + * Wrapper for [duk_has_prop](http://duktape.org/api.html#duk_has_prop). + * + * @param ctx the context + * @param index the object index + * @param position the property index + * @return true if has + */ +inline bool hasProperty(ContextPtr ctx, Index index, unsigned position) +{ + return duk_has_prop_index(ctx, index, position); +} + +/** + * Wrapper for [duk_has_prop](http://duktape.org/api.html#duk_has_prop). + * + * @param ctx the context + * @param index the object index + * @param name the property name + * @return true if has + */ +inline bool hasProperty(ContextPtr ctx, int index, const std::string &name) +{ + return duk_has_prop_string(ctx, index, name.c_str()); +} + +/** + * Wrapper for [duk_insert](http://duktape.org/api.html#duk_insert). + * + * @param ctx the context + * @param to the destination + * @note Wrapper of duk_insert + */ +inline void insert(ContextPtr ctx, Index to) +{ + duk_insert(ctx, to); +} + +/** + * Wrapper for [duk_instanceof](http://duktape.org/api.html#duk_instanceof). + * + * @param ctx the context + * @param idx1 the value to test + * @param idx2 the instance requested + * @return true if idx1 is instance of idx2 + */ +inline bool instanceof(ContextPtr ctx, Index idx1, Index idx2) +{ + return duk_instanceof(ctx, idx1, idx2); +} + +/** + * Wrapper for [duk_join](http://duktape.org/api.html#duk_join). + * + * @param ctx the context + * @param count the number of values + */ +inline void join(ContextPtr ctx, Index count) +{ + duk_join(ctx, count); +} + +/** + * Wrapper for [duk_json_decode](http://duktape.org/api.html#duk_json_decode). + * + * @param ctx the context + * @param index the index + */ +inline void jsonDecode(ContextPtr ctx, Index index) +{ + duk_json_decode(ctx, index); +} + +/** + * Wrapper for [duk_json_encode](http://duktape.org/api.html#duk_json_encode). + * + * @param ctx the context + * @param index the index + */ +inline void jsonEncode(ContextPtr ctx, Index index) +{ + duk_json_encode(ctx, index); +} + +/** + * Wrapper for [duk_normalize_index](http://duktape.org/api.html#duk_normalize_index). + * + * @param ctx the context + * @param index the index + */ +inline Index normalizeIndex(ContextPtr ctx, Index index) +{ + return duk_normalize_index(ctx, index); +} + +/** + * Wrapper for [duk_pcall](http://duktape.org/api.html#duk_pcall). + * + * @param ctx the context + * @param nargs the number of arguments + */ +inline int pcall(ContextPtr ctx, Index nargs = 0) +{ + return duk_pcall(ctx, nargs); +} + +/** + * Wrapper for [duk_peval](http://duktape.org/api.html#duk_peval). + * + * @param ctx the context + */ +inline int peval(ContextPtr ctx) +{ + return duk_peval(ctx); +} + +/** + * Wrapper for [duk_peval_file](http://duktape.org/api.html#duk_peval_file). + * + * @param ctx the context + * @param path the path + * @param result true to get the result at the top of the stack + */ +inline int pevalFile(ContextPtr ctx, const std::string &path, bool result = true) +{ + return result ? duk_peval_file(ctx, path.c_str()) : duk_peval_file_noresult(ctx, path.c_str()); +} + +/** + * Wrapper for [duk_peval_string](http://duktape.org/api.html#duk_peval_string). + * + * @param ctx the context + * @param src the source script + * @param result true to get the result at the top of the stack + */ +inline int pevalString(ContextPtr ctx, const std::string &src, bool result = true) +{ + return result ? duk_peval_string(ctx, src.c_str()) : duk_peval_string_noresult(ctx, src.c_str()); +} + +/** + * Wrapper for [duk_pop_n](http://duktape.org/api.html#duk_pop_n). + * + * @param ctx the context + * @param count the number of values to pop + */ +inline void pop(ContextPtr ctx, Index count = 1) +{ + duk_pop_n(ctx, count); +} + +/** + * Wrapper for [duk_remove](http://duktape.org/api.html#duk_remove). + * + * @param ctx the context + * @param index the value to remove + */ +inline void remove(ContextPtr ctx, Index index) +{ + duk_remove(ctx, index); +} + +/** + * Wrapper for [duk_replace](http://duktape.org/api.html#duk_replace). + * + * @param ctx the context + * @param index the value to replace by the value at the top of the stack + */ +inline void replace(ContextPtr ctx, Index index) +{ + duk_replace(ctx, index); +} + +/** + * Wrapper for [duk_set_prototype](http://duktape.org/api.html#duk_set_prototype). + * + * @param ctx the context + * @param index the value index + */ +inline void setPrototype(ContextPtr ctx, Index index) +{ + duk_set_prototype(ctx, index); +} + +/** + * Wrapper for [duk_swap](http://duktape.org/api.html#duk_swap). + * + * @param ctx the context + * @param index1 the first index + * @param index2 the second index + */ +inline void swap(ContextPtr ctx, Index index1, Index index2) +{ + duk_swap(ctx, index1, index2); +} + +/** + * Wrapper for [duk_swap_top](http://duktape.org/api.html#duk_swap_top). + * + * @param ctx the context + * @param index the index + */ +inline void swapTop(ContextPtr ctx, Index index) +{ + duk_swap_top(ctx, index); +} + +/** + * Wrapper for [duk_get_top](http://duktape.org/api.html#duk_get_top). + * + * @param ctx the context + * @return the stack size + */ +inline int top(ContextPtr ctx) noexcept +{ + return duk_get_top(ctx); +} + +/** + * Wrapper for [duk_get_type](http://duktape.org/api.html#duk_get_type). + * + * @param ctx the context + * @param index the idnex + * @return the type + */ +inline int type(ContextPtr ctx, Index index) noexcept +{ + return duk_get_type(ctx, index); +} + +/* + * Push / Get / Require / Is / Optional + * ---------------------------------------------------------- + * + * The following functions are used to push, get or check values from the stack. They use specialization + * of TypeTraits class. + */ + +/** + * Push a value into the stack. Calls TypeTraits<T>::push(*this, value); + * + * @param value the value to forward + */ +template <typename Type> +inline void push(ContextPtr ctx, Type &&value) +{ + TypeTraits<std::decay_t<Type>>::push(ctx, std::forward<Type>(value)); +} + +/** + * Generic template function to get a value from the stack. + * + * @param index the index + * @return the value + */ +template <typename Type> +inline auto get(ContextPtr ctx, int index) -> decltype(TypeTraits<Type>::get(ctx, 0)) +{ + return TypeTraits<Type>::get(ctx, index); +} + +/** + * Require a type at the specified index. + * + * @param index the index + * @return the value + */ +template <typename Type> +inline auto require(ContextPtr ctx, int index) -> decltype(TypeTraits<Type>::require(ctx, 0)) +{ + return TypeTraits<Type>::require(ctx, index); +} + +/** + * Check if a value is a type of T. + * + * The TypeTraits<T> must have `static bool is(ContextPtr ptr, int index)`. + * + * @param index the value index + * @return true if is the type + */ +template <typename T> +inline bool is(ContextPtr ctx, int index) +{ + return TypeTraits<T>::is(ctx, index); +} + +/** + * Get an optional value from the stack, if the value is not available of not the correct type, + * return defaultValue instead. + * + * The TypeTraits<T> must have `static T optional(Context &, int index, T &&defaultValue)`. + * + * @param index the value index + * @param defaultValue the value replacement + * @return the value or defaultValue + */ +template <typename Type> +inline auto optional(ContextPtr ctx, int index, Type &&defaultValue) +{ + return TypeTraits<std::decay_t<Type>>::optional(ctx, index, std::forward<Type>(defaultValue)); +} + +/* + * Properties management + * ---------------------------------------------------------- + * + * The following functions are used to read or set properties on objects or globals also using TypeTraits. + */ + +/** + * Get the property `name' as value from the object at the specified index. + * + * @param index the object index + * @param name the property name + * @return the value + * @note The stack is unchanged + */ +template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr> +inline auto getProperty(ContextPtr ctx, int index, const std::string &name) -> decltype(get<Type>(ctx, 0)) +{ + duk_get_prop_string(ctx, index, name.c_str()); + decltype(get<Type>(ctx, 0)) value = get<Type>(ctx, -1); + duk_pop(ctx); + + return value; +} + +/** + * Get a property by index, for arrays. + * + * @param index the object index + * @param position the position int the object + * @return the value + * @note The stack is unchanged + */ +template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr> +inline auto getProperty(ContextPtr ctx, int index, int position) -> decltype(get<Type>(ctx, 0)) +{ + duk_get_prop_index(ctx, index, position); + decltype(get<Type>(ctx, 0)) value = get<Type>(ctx, -1); + duk_pop(ctx); + + return value; +} + +/** + * Get the property `name' and push it to the stack from the object at the specified index. + * + * @param index the object index + * @param name the property name + * @note The stack contains the property value + */ +template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr> +inline void getProperty(ContextPtr ctx, int index, const std::string &name) +{ + duk_get_prop_string(ctx, index, name.c_str()); +} + +/** + * Get the property by index and push it to the stack from the object at the specified index. + * + * @param index the object index + * @param position the position in the object + * @note The stack contains the property value + */ +template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr> +inline void getProperty(ContextPtr ctx, int index, int position) +{ + duk_get_prop_index(ctx, index, position); +} + +/** + * Get an optional property `name` from the object at the specified index. + * + * @param index the object index + * @param name the property name + * @param def the default value + * @return the value or def + * @note The stack is unchanged + */ +template <typename Type, typename DefaultValue> +inline auto optionalProperty(ContextPtr ctx, int index, const std::string &name, DefaultValue &&def) -> decltype(optional(ctx, 0, std::forward<DefaultValue>(def))) +{ + duk_get_prop_string(ctx, index, name.c_str()); + decltype(optional(ctx, 0, std::forward<DefaultValue>(def))) value = optional(ctx, -1, std::forward<DefaultValue>(def)); + duk_pop(ctx); + + return value; +} + +/** + * Get an optional property by index, for arrays + * + * @param index the object index + * @param position the position int the object + * @param def the default value + * @return the value or def + * @note The stack is unchanged + */ +template <typename Type, typename DefaultValue> +inline auto optionalProperty(ContextPtr ctx, int index, int position, DefaultValue &&def) -> decltype(optional(ctx, 0, std::forward<DefaultValue>(def))) +{ + duk_get_prop_index(ctx, index, position); + decltype(optional(ctx, 0, std::forward<DefaultValue>(def))) value = optional(ctx, -1, std::forward<DefaultValue>(def)); + duk_pop(ctx); + + return value; +} + +/** + * Set a property to the object at the specified index. + * + * @param index the object index + * @param name the property name + * @param value the value to forward + * @note The stack is unchanged + */ +template <typename Type> +void putProperty(ContextPtr ctx, int index, const std::string &name, Type &&value) +{ + index = duk_normalize_index(ctx, index); + + push(ctx, std::forward<Type>(value)); + duk_put_prop_string(ctx, index, name.c_str()); +} + +/** + * Set a property by index, for arrays. + * + * @param index the object index + * @param position the position in the object + * @param value the value to forward + * @note The stack is unchanged + */ +template <typename Type> +void putProperty(ContextPtr ctx, int index, int position, Type &&value) +{ + index = duk_normalize_index(ctx, index); + + push(ctx, std::forward<Type>(value)); + duk_put_prop_index(ctx, index, position); +} + +/** + * Put the value that is at the top of the stack as property to the object. + * + * @param index the object index + * @param name the property name + */ +inline void putProperty(ContextPtr ctx, int index, const std::string &name) +{ + duk_put_prop_string(ctx, index, name.c_str()); +} + +/** + * Put the value that is at the top of the stack to the object as index. + * + * @param index the object index + * @param position the position in the object + */ +inline void putProperty(ContextPtr ctx, int index, int position) +{ + duk_put_prop_index(ctx, index, position); +} + +/** + * Get a global value. + * + * @param name the name of the global variable + * @return the value + */ +template <typename Type> +inline auto getGlobal(ContextPtr ctx, const std::string &name, std::enable_if_t<!std::is_void<Type>::value> * = nullptr) -> decltype(get<Type>(ctx, 0)) +{ + duk_get_global_string(ctx, name.c_str()); + decltype(get<Type>(ctx, 0)) value = get<Type>(ctx, -1); + duk_pop(ctx); + + return value; +} + +/** + * Overload that push the value at the top of the stack instead of returning it. + */ +template <typename Type> +inline void getGlobal(ContextPtr ctx, const std::string &name, std::enable_if_t<std::is_void<Type>::value> * = nullptr) noexcept +{ + duk_get_global_string(ctx, name.c_str()); +} + +/** + * Set a global variable. + * + * @param name the name of the global variable + * @param type the value to set + */ +template <typename Type> +inline void putGlobal(ContextPtr ctx, const std::string &name, Type&& type) +{ + push(ctx, std::forward<Type>(type)); + duk_put_global_string(ctx, name.c_str()); +} + +/** + * Put the value at the top of the stack as global property. + * + * @param name the property name + */ +inline void putGlobal(ContextPtr ctx, const std::string &name) +{ + duk_put_global_string(ctx, name.c_str()); +} + +/* + * Extra functions + * ---------------------------------------------------------- + * + * The following functions are implemented for convenience and do not exists in the native Duktape API. + */ + +/** + * Enumerate an object or an array at the specified index. + * + * @param index the object or array index + * @param flags the optional flags to pass to duk_enum + * @param getvalue set to true if you want to extract the value + * @param func the function to call for each properties + */ +template <typename Func> +void enumerate(ContextPtr ctx, int index, duk_uint_t flags, duk_bool_t getvalue, Func &&func) +{ + duk_enum(ctx, index, flags); + + while (duk_next(ctx, -1, getvalue)) { + func(ctx); + duk_pop_n(ctx, 1 + (getvalue ? 1 : 0)); + } + + duk_pop(ctx); +} + +/** + * Return the this binding of the current function. + * + * @return the this binding as the template given + */ +template <typename T> +inline auto self(ContextPtr ctx) -> decltype(TypeTraits<T>::get(ctx, 0)) +{ + duk_push_this(ctx); + decltype(TypeTraits<T>::get(ctx, 0)) value = TypeTraits<T>::get(ctx, -1); + duk_pop(ctx); + + return value; +} + +/** + * Throw an ECMAScript exception. + * + * @param ex the exception + */ +template <typename Exception> +void raise(ContextPtr ctx, const Exception &ex) +{ + ex.raise(ctx); +} + +/** + * Wrapper for duk_throw. + * + * @param ctx the context + */ +inline void raise(ContextPtr ctx) +{ + duk_throw(ctx); +} + +/** + * Wrapper for duk_error. + * + * @param ctx the context + * @param type the error type (e.g. DUK_ERR_REFERENCE_ERROR) + * @param fmt the format string + * @param args the arguments + */ +template <typename... Args> +inline void raise(ContextPtr ctx, int type, const char *fmt, Args&&... args) +{ + duk_error(ctx, type, fmt, std::forward<Args>(args)...); +} + +/** + * Wrapper for duk_new. + * + * @param ctx the context + * @param nargs the number of arguments + */ +inline void create(ContextPtr ctx, int nargs = 0) +{ + duk_new(ctx, nargs); +} + +/** + * Construct the object in place, setting value as this binding. + * + * The TypeTraits<T> must have the following requirements: + * + * - static void construct(Context &, T): must update this with the value and keep the stack unchanged + * + * @param value the value to forward + * @see self + */ +template <typename T> +inline void construct(ContextPtr ctx, T &&value) +{ + TypeTraits<std::decay_t<T>>::construct(ctx, std::forward<T>(value)); +} + +/** + * Sign the given object with the name from T. + * + * This is automatically done for when constructing/pushing object with Shared and Pointer helpers, however you need + * to manually add it when using inheritance. + */ +template <typename T> +inline void sign(ContextPtr ctx, Index index) +{ + StackAssert sa(ctx, 0); + + index = duk_normalize_index(ctx, index); + + duk_push_string(ctx, TypeTraits<T>::name().c_str()); + duk_push_boolean(ctx, true); + duk_def_prop(ctx, index < 0 ? index : index, DUK_DEFPROP_HAVE_VALUE); + + /* Do for inherited classes */ + for (const std::string &parent : TypeTraits<T>::inherits()) { + duk_push_string(ctx, parent.c_str()); + duk_push_boolean(ctx, true); + duk_def_prop(ctx, index < 0 ? index : index, DUK_DEFPROP_HAVE_VALUE); + } +} + +/** + * Check if the object at the given index is signed by T or raise TypeError if not. + * + * @param ctx the context + * @param index the index + * @see sign + */ +template <typename T> +inline void checkSignature(ContextPtr ctx, Index index) +{ + StackAssert sa(ctx, 0); + + if (!is<Object>(ctx, index) || !getProperty<bool>(ctx, index, TypeTraits<T>::name())) + raise(ctx, DUK_ERR_TYPE_ERROR, "invalid this binding"); +} + +/** + * Tells if the object at the specified index is of type T. + * + * @param ctx the context + * @param index the index + */ +template <typename T> +inline bool isSigned(ContextPtr ctx, Index index) +{ + StackAssert sa(ctx, 0); + + return is<Object>(ctx, index) && getProperty<bool>(ctx, index, TypeTraits<T>::name()); +} + +/* ------------------------------------------------------------------ + * Exception handling + * ------------------------------------------------------------------ */ + +/** + * @class Error + * @brief Base ECMAScript error class. + * @warning Override the function create for your own exceptions + */ +class Error { +private: + int m_type{DUK_ERR_ERROR}; + std::string m_message; + +protected: + /** + * Constructor with a type of error specified, specially designed for derived errors. + * + * @param type of error (e.g. DUK_ERR_ERROR) + * @param message the message + */ + inline Error(int type, std::string message) noexcept + : m_type(type) + , m_message(std::move(message)) + { + } + +public: + /** + * Constructor with a message. + * + * @param message the message + */ + inline Error(std::string message) noexcept + : m_message(std::move(message)) + { + } + + /** + * Create the exception on the stack. + * + * @note the default implementation search for the global variables + * @param ctx the context + */ + virtual void raise(ContextPtr ctx) const noexcept + { + duk_error(ctx, m_type, "%s", m_message.c_str()); + } +}; + +/** + * @class EvalError + * @brief Error in eval() function. + */ +class EvalError : public Error { +public: + /** + * Construct an EvalError. + * + * @param message the message + */ + inline EvalError(std::string message) noexcept + : Error(DUK_ERR_EVAL_ERROR, std::move(message)) + { + } +}; + +/** + * @class RangeError + * @brief Value is out of range. + */ +class RangeError : public Error { +public: + /** + * Construct an RangeError. + * + * @param message the message + */ + inline RangeError(std::string message) noexcept + : Error(DUK_ERR_RANGE_ERROR, std::move(message)) + { + } +}; + +/** + * @class ReferenceError + * @brief Trying to use a variable that does not exist. + */ +class ReferenceError : public Error { +public: + /** + * Construct an ReferenceError. + * + * @param message the message + */ + inline ReferenceError(std::string message) noexcept + : Error(DUK_ERR_REFERENCE_ERROR, std::move(message)) + { + } +}; + +/** + * @class SyntaxError + * @brief Syntax error in the script. + */ +class SyntaxError : public Error { +public: + /** + * Construct an SyntaxError. + * + * @param message the message + */ + inline SyntaxError(std::string message) noexcept + : Error(DUK_ERR_SYNTAX_ERROR, std::move(message)) + { + } +}; + +/** + * @class TypeError + * @brief Invalid type given. + */ +class TypeError : public Error { +public: + /** + * Construct an TypeError. + * + * @param message the message + */ + inline TypeError(std::string message) noexcept + : Error(DUK_ERR_TYPE_ERROR, std::move(message)) + { + } +}; + +/** + * @class URIError + * @brief URI manipulation failure. + */ +class URIError : public Error { +public: + /** + * Construct an URIError. + * + * @param message the message + */ + inline URIError(std::string message) noexcept + : Error(DUK_ERR_URI_ERROR, std::move(message)) + { + } +}; + +/* ------------------------------------------------------------------ + * Standard overloads for TypeTraits<T> + * ------------------------------------------------------------------ */ + +/** + * @class TypeTraits<int> + * @brief Default implementation for int. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeTraits<int> { +public: + /** + * Get an integer, return 0 if not an integer. + * + * @param ctx the context + * @param index the index + * @return the integer + */ + static inline int get(ContextPtr ctx, int index) + { + return duk_get_int(ctx, index); + } + + /** + * Check if value is an integer. + * + * @param ctx the context + * @param index the index + * @return true if integer + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_number(ctx, index); + } + + /** + * Get an integer, return defaultValue if the value is not an integer. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the integer or defaultValue + */ + static inline int optional(ContextPtr ctx, int index, int defaultValue) + { + return is(ctx, index) ? get(ctx, index) : defaultValue; + } + + /** + * Push an integer. + * + * @param ctx the context + * @param value the value + */ + static inline void push(ContextPtr ctx, int value) + { + duk_push_int(ctx, value); + } + + /** + * Require an integer, throws a JavaScript exception if not an integer. + * + * @param ctx the context + * @param index the index + * @return the integer + */ + static inline int require(ContextPtr ctx, int index) + { + return duk_require_int(ctx, index); + } +}; + +/** + * @class TypeTraits<bool> + * @brief Default implementation for bool. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeTraits<bool> { +public: + /** + * Get a boolean, return 0 if not a boolean. + * + * @param ctx the context + * @param index the index + * @return the boolean + */ + static inline bool get(ContextPtr ctx, int index) + { + return duk_get_boolean(ctx, index); + } + + /** + * Check if value is a boolean. + * + * @param ctx the context + * @param index the index + * @return true if boolean + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_boolean(ctx, index); + } + + /** + * Get a bool, return defaultValue if the value is not a boolean. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the boolean or defaultValue + */ + static inline bool optional(ContextPtr ctx, int index, bool defaultValue) + { + return is(ctx, index) ? get(ctx, index) : defaultValue; + } + + /** + * Push a boolean. + * + * @param ctx the context + * @param value the value + */ + static inline void push(ContextPtr ctx, bool value) + { + duk_push_boolean(ctx, value); + } + + /** + * Require a boolean, throws a JavaScript exception if not a boolean. + * + * @param ctx the context + * @param index the index + * @return the boolean + */ + static inline bool require(ContextPtr ctx, int index) + { + return duk_require_boolean(ctx, index); + } +}; + +/** + * @class TypeTraits<double> + * @brief Default implementation for double. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeTraits<double> { +public: + /** + * Get a double, return 0 if not a double. + * + * @param ctx the context + * @param index the index + * @return the double + */ + static inline double get(ContextPtr ctx, int index) + { + return duk_get_number(ctx, index); + } + + /** + * Check if value is a double. + * + * @param ctx the context + * @param index the index + * @return true if double + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_number(ctx, index); + } + + /** + * Get a double, return defaultValue if the value is not a double. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the double or defaultValue + */ + static inline double optional(ContextPtr ctx, int index, double defaultValue) + { + return is(ctx, index) ? get(ctx, index) : defaultValue; + } + + /** + * Push a double. + * + * @param ctx the context + * @param value the value + */ + static inline void push(ContextPtr ctx, double value) + { + duk_push_number(ctx, value); + } + + /** + * Require a double, throws a JavaScript exception if not a double. + * + * @param ctx the context + * @param index the index + * @return the double + */ + static inline double require(ContextPtr ctx, int index) + { + return duk_require_number(ctx, index); + } +}; + +/** + * @class TypeTraits<std::string> + * @brief Default implementation for std::string. + * + * Provides: get, is, optional, push, require. + * + * Note: the functions allows embedded '\0'. + */ +template <> +class TypeTraits<std::string> { +public: + /** + * Get a string, return 0 if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline std::string get(ContextPtr ctx, int index) + { + duk_size_t size; + const char *text = duk_get_lstring(ctx, index, &size); + + return std::string{text, size}; + } + + /** + * Check if value is a string. + * + * @param ctx the context + * @param index the index + * @return true if string + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_string(ctx, index); + } + + /** + * Get a string, return defaultValue if the value is not an string. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the string or defaultValue + */ + static inline std::string optional(ContextPtr ctx, int index, std::string defaultValue) + { + return is(ctx, index) ? get(ctx, index) : defaultValue; + } + + /** + * Push a string. + * + * @param ctx the context + * @param value the value + */ + static inline void push(ContextPtr ctx, const std::string &value) + { + duk_push_lstring(ctx, value.c_str(), value.length()); + } + + /** + * Require a string, throws a JavaScript exception if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline std::string require(ContextPtr ctx, int index) + { + duk_size_t size; + const char *text = duk_require_lstring(ctx, index, &size); + + return std::string{text, size}; + } +}; + +/** + * @class TypeTraits<const char *> + * @brief Default implementation for const char literals. + * + * Provides: get, is, optional, push, require. + */ +template <> +class TypeTraits<const char *> { +public: + /** + * Get a string, return 0 if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline const char *get(ContextPtr ctx, int index) + { + return duk_get_string(ctx, index); + } + + /** + * Check if value is a string. + * + * @param ctx the context + * @param index the index + * @return true if string + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_string(ctx, index); + } + + /** + * Get an integer, return defaultValue if the value is not an integer. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the integer or defaultValue + */ + static inline const char *optional(ContextPtr ctx, int index, const char *defaultValue) + { + return is(ctx, index) ? get(ctx, index) : defaultValue; + } + + /** + * Push a string. + * + * @param ctx the context + * @param value the value + */ + static inline void push(ContextPtr ctx, const char *value) + { + duk_push_string(ctx, value); + } + + /** + * Require a string, throws a JavaScript exception if not a string. + * + * @param ctx the context + * @param index the index + * @return the string + */ + static inline const char *require(ContextPtr ctx, int index) + { + return duk_require_string(ctx, index); + } +}; + +/** + * @brief Implementation for non-managed pointers. + * + * Provides: get, is, optional, push, require. + */ +template <typename T> +class TypeTraits<RawPointer<T>> { +public: + /** + * Get a pointer, return nullptr if not a pointer. + * + * @param ctx the context + * @param index the index + * @return the pointer + */ + static inline T *get(ContextPtr ctx, int index) + { + return static_cast<T *>(duk_to_pointer(ctx, index)); + } + + /** + * Check if value is a pointer. + * + * @param ctx the context + * @param index the index + * @return true if pointer + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_pointer(ctx, index); + } + + /** + * Get a pointer, return defaultValue if the value is not a pointer. + * + * @param ctx the context + * @param index the index + * @param defaultValue the defaultValue + * @return the pointer or defaultValue + */ + static inline T *optional(ContextPtr ctx, int index, RawPointer<T> defaultValue) + { + return is(ctx, index) ? get(ctx, index) : defaultValue.object; + } + + /** + * Push a pointer. + * + * @param ctx the context + * @param value the value + */ + static inline void push(ContextPtr ctx, const RawPointer<T> &value) + { + duk_push_pointer(ctx, value.object); + } + + /** + * Require a pointer, throws a JavaScript exception if not a pointer. + * + * @param ctx the context + * @param index the index + * @return the pointer + */ + static inline T *require(ContextPtr ctx, int index) + { + return static_cast<T *>(duk_require_pointer(ctx, index)); + } +}; + +/** + * @class TypeTraits<Function> + * @brief Push C++ function to the stack. + * + * Provides: push. + * + * This implementation push a Duktape/C function that is wrapped as C++ for convenience. + */ +template <> +class TypeTraits<Function> { +public: + /** + * Check if the value at the given index is callable. + * + * @param ctx the context + * @param index the value index + * @return true if the value is callable + */ + static bool is(ContextPtr ctx, Index index) + { + return duk_is_callable(ctx, index); + } + + /** + * Push the C++ function, it is wrapped as Duktape/C function and allocated on the heap by moving the + * std::function. + * + * @param ctx the context + * @param fn the function + */ + static void push(ContextPtr ctx, Function fn) + { + duk_push_c_function(ctx, fn.function, fn.nargs); + } +}; + +/** + * @class TypeTraits<FunctionMap> + * @brief Put the functions to the object at the top of the stack. + * + * Provides: push. + */ +template <> +class TypeTraits<FunctionMap> { +public: + /** + * Push all functions to the object at the top of the stack. + * + * @param ctx the context + * @param map the map of function + */ + static inline void push(ContextPtr ctx, const FunctionMap &map) + { + StackAssert sa(ctx, 0); + + for (const auto &entry : map) { + duk_push_c_function(ctx, entry.second.function, entry.second.nargs); + duk_put_prop_string(ctx, -2, entry.first.c_str()); + } + } +}; + +/** + * @class TypeTraits<Object> + * @brief Push empty object to the stack. + * + * Provides: is, push. + */ +template <> +class TypeTraits<Object> { +public: + /** + * Check if value is an object. + * + * @param ctx the context + * @param index the index + * @return true if object + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_object(ctx, index); + } + + /** + * Create an empty object on the stack. + * + * @param ctx the context + */ + static inline void push(ContextPtr ctx, const Object &) + { + duk_push_object(ctx); + } +}; + +/** + * @class TypeTraits<Array> + * @brief Push empty array to the stack. + * + * Provides: is, push. + */ +template <> +class TypeTraits<Array> { +public: + /** + * Check if value is a array. + * + * @param ctx the context + * @param index the index + * @return true if array + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_array(ctx, index); + } + + /** + * Create an empty array on the stack. + * + * @param ctx the context + */ + static inline void push(ContextPtr ctx, const Array &) + { + duk_push_array(ctx); + } +}; + +/** + * @class TypeTraits<Undefined> + * @brief Push undefined value to the stack. + * + * Provides: is, push. + */ +template <> +class TypeTraits<Undefined> { +public: + /** + * Check if value is undefined. + * + * @param ctx the context + * @param index the index + * @return true if undefined + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_undefined(ctx, index); + } + + /** + * Push undefined value on the stack. + * + * @param ctx the context + */ + static inline void push(ContextPtr ctx, const Undefined &) + { + duk_push_undefined(ctx); + } +}; + +/** + * @class TypeTraits<Null> + * @brief Push null value to the stack. + * + * Provides: is, push. + */ +template <> +class TypeTraits<Null> { +public: + /** + * Check if value is null. + * + * @param ctx the context + * @param index the index + * @return true if null + */ + static inline bool is(ContextPtr ctx, int index) + { + return duk_is_null(ctx, index); + } + + /** + * Push null value on the stack. + * + * @param ctx the context + */ + static inline void push(ContextPtr ctx, const Null &) + { + duk_push_null(ctx); + } +}; + +/** + * @brief Push this binding into the stack. + * + * Provides: push. + */ +template <> +class TypeTraits<This> { +public: + /** + * Push this function into the stack. + * + * @param ctx the context + */ + static inline void push(ContextPtr ctx, const This &) + { + duk_push_this(ctx); + } +}; + +/** + * @class TypeTraits<Global> + * @brief Push the global object to the stack. + * + * Provides: push. + */ +template <> +class TypeTraits<Global> { +public: + /** + * Push the global object into the stack. + * + * @param ctx the context + */ + static inline void push(ContextPtr ctx, const Global &) + { + duk_push_global_object(ctx); + } +}; + +/** + * @brief Push a map of key-value pair as objects. + * + * Provides: push. + * + * This class is convenient for settings constants such as enums, string and such. + */ +template <typename T> +class TypeTraits<std::unordered_map<std::string, T>> { +public: + /** + * Put all values from the map as properties to the object at the top of the stack. + * + * @param ctx the context + * @param map the values + * @note You need an object at the top of the stack before calling this function + */ + static void push(ContextPtr ctx, const std::unordered_map<std::string, T> &map) + { + StackAssert sa(ctx, 1); + + duk_push_object(ctx); + + for (const auto &pair : map) { + TypeTraits<T>::push(ctx, pair.second); + duk_put_prop_string(ctx, -2, pair.first.c_str()); + } + } +}; + +/** + * @brief Push or get vectors as JavaScript arrays. + * + * Provides: get, push. + */ +template <typename T> +class TypeTraits<std::vector<T>> { +public: + /** + * Get an array from the stack. + * + * @param ctx the context + * @param index the array index + * @return the array or empty array if the value is not an array + */ + static std::vector<T> get(ContextPtr ctx, int index) + { + StackAssert sa(ctx, 0); + + std::vector<T> result; + + if (!duk_is_array(ctx, -1)) + return result; + + int total = duk_get_length(ctx, index); + + for (int i = 0; i < total; ++i) + result.push_back(getProperty<T>(ctx, index, i)); + + return result; + } + + /** + * Create an array with the specified values. + * + * @param ctx the context + * @param array the values + */ + static void push(ContextPtr ctx, const std::vector<T> &array) + { + StackAssert sa(ctx, 1); + + duk_push_array(ctx); + + unsigned i = 0; + for (const auto &v : array) { + TypeTraits<T>::push(ctx, v); + duk_put_prop_index(ctx, -2, i++); + } + } +}; + +/** + * @brief Implementation of managed shared_ptr + * @see Shared + */ +template <typename T> +class TypeTraits<Shared<T>> { +private: + static void apply(ContextPtr ctx, std::shared_ptr<T> value) + { + StackAssert sa(ctx, 0); + + sign<T>(ctx, -1); + + duk_push_pointer(ctx, new std::shared_ptr<T>(std::move(value))); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-shared-ptr"); + duk_push_c_function(ctx, [] (duk_context *ctx) -> Ret { + duk_get_prop_string(ctx, 0, "\xff""\xff""js-shared-ptr"); + delete static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + duk_push_null(ctx); + duk_put_prop_string(ctx, 0, "\xff""\xff""js-ptr"); + + return 0; + }, 1); + duk_set_finalizer(ctx, -2); + } + +public: + /** + * Construct the shared_ptr as this. + * + * @param ctx the context + * @param value the value + */ + static void construct(ContextPtr ctx, Shared<T> value) + { + StackAssert sa(ctx, 0); + + duk_push_this(ctx); + apply(ctx, std::move(value.object)); + duk_pop(ctx); + } + + /** + * Push a managed shared_ptr as object. + * + * @param ctx the context + * @param value the value + */ + static void push(ContextPtr ctx, Shared<T> value) + { + StackAssert sa(ctx, 1); + + duk_push_object(ctx); + apply(ctx, value.object); + TypeTraits<T>::prototype(ctx); + duk_set_prototype(ctx, -2); + } + + /** + * Get a managed shared_ptr from the stack. + * + * @param ctx the context + * @param index the object index + * @return the shared_ptr + */ + static std::shared_ptr<T> get(ContextPtr ctx, int index) + { + StackAssert sa(ctx, 0); + + checkSignature<T>(ctx, index); + + duk_get_prop_string(ctx, index, "\xff""\xff""js-shared-ptr"); + std::shared_ptr<T> value = *static_cast<std::shared_ptr<T> *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + + return value; + } +}; + +/** + * @brief Implementation of managed pointers + * @see Pointer + */ +template <typename T> +class TypeTraits<Pointer<T>> { +private: + static void apply(ContextPtr ctx, T *value) + { + StackAssert sa(ctx, 0); + + sign<T>(ctx, -1); + + duk_push_pointer(ctx, value); + duk_put_prop_string(ctx, -2, "\xff""\xff""js-ptr"); + duk_push_c_function(ctx, [] (duk_context *ctx) -> Ret { + duk_get_prop_string(ctx, 0, "\xff""\xff""js-ptr"); + delete static_cast<T *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + duk_push_null(ctx); + duk_put_prop_string(ctx, 0, "\xff""\xff""js-ptr"); + + return 0; + }, 1); + duk_set_finalizer(ctx, -2); + } + +public: + /** + * Construct the pointer as this. + * + * @param ctx the context + * @param value the value + */ + static void construct(ContextPtr ctx, Pointer<T> value) + { + StackAssert sa(ctx, 0); + + duk_push_this(ctx); + apply(ctx, value.object); + duk_pop(ctx); + } + + /** + * Push a managed pointer as object. + * + * @param ctx the context + * @param value the value + */ + static void push(ContextPtr ctx, Pointer<T> value) + { + StackAssert sa(ctx, 1); + + duk_push_object(ctx); + apply(ctx, value.object); + TypeTraits<T>::prototype(ctx); + duk_set_prototype(ctx, -2); + } + + /** + * Get a managed pointer from the stack. + * + * @param ctx the context + * @param index the object index + * @return the pointer + * @warning Do not store the pointer into the C++ side, the object can be deleted at any time + */ + static T *get(ContextPtr ctx, int index) + { + StackAssert sa(ctx, 0); + + checkSignature<T>(ctx, index); + + duk_get_prop_string(ctx, index, "\xff""\xff""js-ptr"); + T *value = static_cast<T *>(duk_to_pointer(ctx, -1)); + duk_pop(ctx); + + return value; + } +}; + +} // !duk + +} // !malikania + +#endif // !MALIKANIA_JS_H