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