view modules/js/duktape.hpp @ 540:fd2ba28ac54b

Js: add dukx_(get|push)_object
author David Demelier <markand@malikania.fr>
date Wed, 08 Jun 2016 22:12:58 +0200
parents 819a14133f2e
children f48bb09bccc7
line wrap: on
line source

/*
 * duktape.hpp -- Duktape extras
 *
 * 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 DUKTAPE_HPP
#define 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>

/**
 * \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:
	duk_context *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(duk_context *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)
		if (static_cast<unsigned>(duk_get_top(m_context)) - m_begin != m_expected) {
			std::fprintf(stderr, "Corrupt stack detection in StackAssert:\n");
			std::fprintf(stderr, "  Size at start:            %u\n", m_begin);
			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_begin);
			std::fprintf(stderr, "  Number of stale values:   %u\n", duk_get_top(m_context) - m_begin - m_expected);
			std::abort();
		}
#endif
	}
};

/**
 * \class Exception
 * \brief Error description.
 *
 * This class fills the fields got in an Error object.
 */
class 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 UniqueContext {
private:
	using Deleter = void (*)(duk_context *);
	using Handle = std::unique_ptr<duk_context, Deleter>;

	Handle m_handle;

	UniqueContext(const UniqueContext &) = delete;
	UniqueContext &operator=(const UniqueContext &) = delete;

public:
	/**
	 * Create default context.
	 */
	inline UniqueContext()
		: m_handle(duk_create_heap_default(), duk_destroy_heap)
	{
	}

	/**
	 * Default move constructor.
	 */
	UniqueContext(UniqueContext &&) 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
	 */
	UniqueContext &operator=(UniqueContext &&) noexcept = delete;
};

/**
 * \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(duk_context *ctx) const
	{
		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))
	{
	}
};

/**
 * 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 Exception dukx_exception(duk_context *ctx, int index, bool pop = true)
{
	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;
}

/**
 * Enumerate an object or an array at the specified index.
 *
 * \param ctx the context
 * \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 dukx_enumerate(duk_context *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);
}

/**
 * Throw an ECMAScript exception.
 *
 * \param ctx the context
 * \param ex the exception
 */
template <typename Exception>
void dukx_throw(duk_context *ctx, const Exception &ex)
{
	ex.raise(ctx);
}

/**
 * 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 // !DUKTAPE_HPP