Mercurial > malikania
view libcommon-js/malikania/duktape.hpp @ 76:858621081b95
Happy new year!
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sun, 01 Jan 2017 13:35:37 +0100 |
parents | f8cb71805a4b |
children | 4b292c20124c |
line wrap: on
line source
/* * duktape.hpp -- Duktape extras * * Copyright (c) 2016-2017 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_COMMON_DUKTAPE_HPP #define MALIKANIA_COMMON_DUKTAPE_HPP /** * \file duktape.hpp * \brief Bring some extras to Duktape C library. * \author David Demelier <markand@malikania.fr> */ #include <cstdio> #include <cstdlib> #include <memory> #include <string> #include <unordered_map> #include <utility> #include <vector> #include <duktape.h> /** * \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 dukx_stack_assert { #if !defined(NDEBUG) private: duk_context* m_context; unsigned m_expected; int m_at_start; #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 dukx_stack_assert(duk_context *ctx, unsigned expected = 0) noexcept #if !defined(NDEBUG) : m_context(ctx) , m_expected(expected) , m_at_start(duk_get_top(ctx)) #endif { #if defined(NDEBUG) (void)ctx; (void)expected; #endif } /** * Verify the expected size. * * No-op if NDEBUG is set. */ inline ~dukx_stack_assert() noexcept { #if !defined(NDEBUG) auto result = duk_get_top(m_context) - m_at_start; if (result != static_cast<int>(m_expected)) { std::fprintf(stderr, "Corrupt stack detection in dukx_stack_assert:\n"); std::fprintf(stderr, " Size at start: %d\n", m_at_start); std::fprintf(stderr, " Size at end: %d\n", duk_get_top(m_context)); std::fprintf(stderr, " Expected (user): %u\n", m_expected); std::fprintf(stderr, " Expected (adjusted): %u\n", m_expected + m_at_start); std::fprintf(stderr, " Difference count: %+d\n", result - m_expected); std::abort(); } #endif } }; /** * \brief Error description. * * This class fills the fields got in an Error object. */ class dukx_exception : 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(); } }; /** * \brief RAII based Duktape handler. * * This class is implicitly convertible to duk_context for convenience. */ class dukx_context { private: std::unique_ptr<duk_context, void (*)(duk_context*)> m_handle; dukx_context(const dukx_context&) = delete; dukx_context &operator=(const dukx_context&) = delete; public: /** * Create default context. */ inline dukx_context() noexcept : m_handle(duk_create_heap_default(), duk_destroy_heap) { } /** * Default move constructor. */ dukx_context(dukx_context&&) noexcept = default; /** * 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(); } /** * Default move assignment operator. * * \return this */ dukx_context& operator=(dukx_context&&) noexcept = delete; }; /** * Get the error object when a JavaScript error has been thrown (e.g. eval * failure). * * \param ctx the context * \param index the index * \param pop if true, also remove the exception from the stack * \return the information */ inline dukx_exception dukx_get_exception(duk_context* ctx, int index, bool pop = true) { dukx_exception ex; index = duk_normalize_index(ctx, index); duk_get_prop_string(ctx, index, "name"); ex.name = duk_to_string(ctx, -1); duk_get_prop_string(ctx, index, "message"); ex.message = duk_to_string(ctx, -1); duk_get_prop_string(ctx, index, "fileName"); ex.fileName = duk_to_string(ctx, -1); duk_get_prop_string(ctx, index, "lineNumber"); ex.lineNumber = duk_to_int(ctx, -1); duk_get_prop_string(ctx, index, "stack"); ex.stack = duk_to_string(ctx, -1); duk_pop_n(ctx, 5); if (pop) { duk_remove(ctx, index); } return ex; } /** * Get a string, return 0 if not a string. * * \param ctx the context * \param index the index * \return the string */ inline std::string dukx_get_std_string(duk_context* ctx, int index) { duk_size_t size; const char* text = duk_get_lstring(ctx, index, &size); return std::string(text, size); } /** * Require a string, throws a JavaScript exception if not a string. * * \param ctx the context * \param index the index * \return the string */ inline std::string dukx_require_std_string(duk_context* ctx, int index) { duk_size_t size; const char* text = duk_require_lstring(ctx, index, &size); return std::string(text, size); } /** * Push a C++ string. * * \param ctx the context * \param str the string */ inline void dukx_push_std_string(duk_context* ctx, const std::string& str) { duk_push_lstring(ctx, str.data(), str.length()); } /** * Get an array. * * \param ctx the context * \param index the array index * \param get the conversion function (e.g. duk_get_int) */ template <typename Getter> auto dukx_get_array(duk_context* ctx, duk_idx_t index, Getter&& get) { using T = decltype(get(ctx, 0)); std::vector<T> result; std::size_t length = duk_get_length(ctx, index); for (std::size_t i = 0; i < length; ++i) { duk_get_prop_index(ctx, -1, i); result.push_back(get(ctx, -1)); duk_pop(ctx); } return result; } /** * Push an array. * * \param ctx the context * \param values the values * \param push the function to push values */ template <typename T, typename Pusher> void dukx_push_array(duk_context* ctx, const std::vector<T>& values, Pusher&& push) { duk_push_array(ctx); int i = 0; for (auto x : values) { push(ctx, x); duk_put_prop_index(ctx, -2, i++); } } /** * Get an object. * * \param ctx the context * \param index the object index * \param get the conversion function (e.g. duk_get_int) */ template <typename Getter> auto dukx_get_object(duk_context* ctx, duk_idx_t index, Getter&& get) { using T = decltype(get(ctx, 0)); std::unordered_map<std::string, T> result; duk_enum(ctx, index, 0); while (duk_next(ctx, -1, true)) { result.emplace(dukx_get_std_string(ctx, -2), get(ctx, -1)); duk_pop_2(ctx); } duk_pop(ctx); return result; } /** * Push an object. * * \param ctx the context * \param values the values * \param push the function to push values */ template <typename T, typename Pusher> void dukx_push_object(duk_context* ctx, const std::unordered_map<std::string, T>& values, Pusher&& push) { duk_push_object(ctx); for (const auto& pair : values) { push(ctx, pair.second); duk_put_prop_string(ctx, -2, pair.first.c_str()); } } #endif // !MALIKANIA_COMMON_DUKTAPE_HPP