view C++/modules/Js/Js.h @ 391:e2cefd0ee511

Add Js, Duktape wrapper
author David Demelier <markand@malikania.fr>
date Mon, 28 Sep 2015 13:18:14 +0200
parents
children 69adcefe73ae
line wrap: on
line source

/*
 * Js.h -- JavaScript wrapper for Duktape
 *
 * Copyright (c) 2013, 2014, 2015 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 _JS_H_
#define _JS_H_

/**
 * @file Js.h
 * @brief Bring JavaScript using Duktape
 *
 * This file provides usual Duktape function renamed and placed into `js` 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 <cerrno>
#include <cstring>
#include <memory>
#include <stack>
#include <string>
#include <vector>

#include <duktape.h>

namespace js {

#if !defined(NDEBUG)
#define assertBegin(ctx)						\
	int _topstack = duk_get_top(ctx)
#else
#define assertBegin(ctx)
#endif

#if !defined(NDEBUG)
#define assertEquals(ctx)						\
	assert(_topstack == duk_get_top(ctx))
#else
#define assertEquals(ctx)
#endif

#if !defined(NDEBUG)
#define assertEnd(ctx, count)					\
	assert(_topstack == (duk_get_top(ctx) - count))
#else
#define assertEnd(ctx, count)
#endif

/**
 * Typedef for readability.
 */
using ContextPtr = duk_context *;

/**
 * @class Context
 * @brief RAII based Duktape handler
 *
 * This class is implicitly convertible to duk_context for convenience.
 */
class Context : public std::unique_ptr<duk_context, void (*)(duk_context *)> {
private:
	/* Move and copy forbidden */
	Context(const Context &) = delete;
	Context &operator=(const Context &) = delete;
	Context(const Context &&) = delete;
	Context &operator=(const Context &&) = delete;

public:
	/**
	 * Create a Duktape context using defaults.
	 */
	Context();

	/**
	 * Convert the context to the native Duktape/C type.
	 *
	 * @return the duk_context
	 */
	inline operator duk_context *() noexcept
	{
		return get();
	}

	/**
	 * Convert the context to the native Duktape/C type.
	 *
	 * @return the duk_context
	 */
	inline operator duk_context *() const noexcept
	{
		return get();
	}
};

/**
 * @class Object
 * @brief Empty class tag for push() function
 */
class Object {
};

/**
 * @class Arary
 * @brief Empty class tag for push() function
 */
class Array {
};

/**
 * @class Function
 * @brief Duktape/C function definition
 */
class Function {
public:
	/**
	 * The function pointer, must not be null.
	 */
	duk_c_function function{nullptr};

	/**
	 * Number of args that the function takes
	 */
	duk_idx_t nargs{0};
};

/**
 * @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();
	}
};

/* --------------------------------------------------------
 * Push functions
 * -------------------------------------------------------- */

/**
 * Push a boolean.
 *
 * @param ctx the context
 * @param value the boolean value
 */
void push(ContextPtr ctx, bool value);

/**
 * Push an integer.
 *
 * @param ctx the context
 * @param value the integer value
 */
void push(ContextPtr ctx, int value);

/**
 * Push a real value.
 *
 * @param ctx the context
 * @param value the value
 */
void push(ContextPtr ctx, double value);

/**
 * Push a string.
 *
 * @param ctx the context
 * @param value the string
 */
void push(ContextPtr ctx, const std::string &value);

/**
 * Push a literal or C-string.
 *
 * @param ctx the context
 * @param value the value
 */
void push(ContextPtr ctx, const char *value);

/**
 * Push a Duktape/C function.
 *
 * @param ctx the context
 * @param function the function
 */
void push(ContextPtr ctx, const Function &function);

/**
 * Push an empty object on the stack.
 *
 * @param ctx the context
 * @param object the empty object
 */
void push(ContextPtr ctx, const Object &object);

/**
 * Push an empty array at the top of the stack.
 *
 * @param ctx the context
 * @param array the empty array
 */
void push(ContextPtr ctx, const Array &array);

/* --------------------------------------------------------
 * Get functions
 * -------------------------------------------------------- */

/**
 * Get a boolean.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store the value
 */
void get(ContextPtr ctx, duk_idx_t index, bool &value);

/**
 * Get an integer.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store the value
 */
void get(ContextPtr ctx, duk_idx_t index, int &value);

/**
 * Get a real.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store the value
 */
void get(ContextPtr ctx, duk_idx_t index, double &value);

/**
 * Get a string.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store the value
 */
void get(ContextPtr ctx, duk_idx_t index, std::string &value);

/**
 * Generic template function to get a value from the stack.
 *
 * @param ctx the context
 * @param index the index
 * @return the value
 */
template <typename Type>
inline Type get(ContextPtr ctx, duk_idx_t index)
{
	Type value;

	get(ctx, index, value);

	return value;
}

/* --------------------------------------------------------
 * Get functions (for object)
 * -------------------------------------------------------- */

template <typename Type>
inline Type getObject(ContextPtr ctx, duk_idx_t index, const std::string &name)
{
	Type type;

	assertBegin(ctx);
	duk_get_prop_string(ctx, index, name.c_str());
	get(ctx, -1, type);
	duk_pop(ctx);
	assertEquals(ctx);

	return type;
}

/* --------------------------------------------------------
 * Set functions (for object)
 * -------------------------------------------------------- */

template <typename Type>
void setObject(ContextPtr ctx, duk_idx_t index, const std::string &name, Type&& value)
{
	index = duk_normalize_index(ctx, index);

	assertBegin(ctx);
	push(ctx, std::forward<Type>(value));
	duk_put_prop_string(ctx, index, name.c_str());
	assertEquals(ctx);
}

/* --------------------------------------------------------
 * Require functions
 * -------------------------------------------------------- */

/**
 * Requires a value or throw an exception if not a boolean.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store the value
 */
void require(ContextPtr ctx, duk_idx_t index, bool &value);

/**
 * Requires a value or throw an exception if not an integer.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store the value
 */
void require(ContextPtr ctx, duk_idx_t index, int &value);

/**
 * Requires a value or throw an exception if not a double.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store the value
 */
void require(ContextPtr ctx, duk_idx_t index, double &value);

/**
 * Requires a value or throw an exception if not a string.
 *
 * @param ctx the context
 * @param index the index
 * @param value the reference where to store string
 */
void require(ContextPtr ctx, duk_idx_t index, std::string &value);

/**
 * Generic function to require a value from the stack.
 *
 * @param ctx the context
 * @param index the specified index
 * @return the value
 */
template <typename Type>
inline Type require(ContextPtr ctx, duk_idx_t index)
{
	Type value;

	require(ctx, index, value);

	return value;
}

/* --------------------------------------------------------
 * Basic functions
 * -------------------------------------------------------- */

/**
 * Get the type of the value at the specified index.
 *
 * @param ctx the context
 * @param index the idnex
 * @return the type
 */
duk_int_t type(ContextPtr ctx, duk_idx_t index);

/**
 * Get the current stack size.
 *
 * @param ctx the context
 * @return the stack size
 */
int top(ContextPtr ctx);

/**
 * Pop a certain number of values from the top of the stack.
 *
 * @param ctx the context
 * @param count the number of values to pop
 * @pre count must be positive
 */
void pop(ContextPtr ctx, int count = 1);

/**
 * Check if idx1 is an instance of idx2.
 *
 * @param ctx the context
 * @param idx1 the value to test
 * @param idx2 the instance requested
 * @return true if idx1 is instance of idx2
 */
duk_bool_t instanceof(ContextPtr ctx, duk_idx_t idx1, duk_idx_t idx2);

/**
 * Call the object at the top of the stack.
 *
 * @param ctx the context
 * @param nargs the number of arguments
 * @note Non-protected
 */
void call(ContextPtr ctx, duk_idx_t nargs = 0);

/**
 * Call in protected mode the object at the top of the stack.
 *
 * @param ctx the context
 * @param nargs the number of arguments
 * @throw ErrorInfo on errors
 */
void pcall(ContextPtr ctx, duk_idx_t nargs = 0);

/* ------------------------------------------------------------------
 * Eval functions
 * ------------------------------------------------------------------ */

/**
 * Evaluate a non-protected chunk at the top of the stack.
 *
 * @param ctx
 */
void eval(ContextPtr ctx);

/**
 * Evaluate a non-protected string script.
 *
 * @param ctx the context
 * @param script the script content
 */
void evalString(ContextPtr ctx, const std::string &script);

/**
 * Evaluate a non-protected file.
 *
 * @param ctx the context
 * @param file the file
 */
void evalFile(ContextPtr ctx, const std::string &file);

/**
 * Evaluate a protected chunk.
 *
 * @param ctx the context
 * @throw ErrorInfo the error
 */
void peval(ContextPtr ctx);

/**
 * Evaluate a protected string script.
 *
 * @param ctx the context
 * @param script the script content
 * @throw ErrorInfo the error
 */
void pevalString(ContextPtr ctx, const std::string &script);

/**
 * Evaluate a protected file.
 *
 * @param ctx the context
 * @param file the file
 * @throw ErrorInfo the error
 */
void pevalFile(ContextPtr ctx, const std::string &file);

/* ------------------------------------------------------------------
 * Extended functions
 * ------------------------------------------------------------------ */

/**
 * 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 enumerate(ContextPtr ctx, duk_idx_t index, duk_uint_t flags, duk_bool_t getvalue, Func&& func)
{
	assertBegin(ctx);
	duk_enum(ctx, index, flags);

	while (duk_next(ctx, -1, getvalue)) {
		func(ctx);
		duk_pop_n(ctx, 1 + (getvalue ? 1 : 0));
	}

	duk_pop(ctx);
	assertEquals(ctx);
}

/* --------------------------------------------------------
 * Global functions
 * -------------------------------------------------------- */

/**
 * Get a global value.
 *
 * @param ctx the context
 * @param name the name of the global variable
 * @return the value
 */
template <typename Type>
inline Type getGlobal(ContextPtr ctx, const std::string &name)
{
	assertBegin(ctx);
	duk_get_global_string(ctx, name.c_str());
	Type value = get<Type>(ctx, -1);
	duk_pop(ctx);
	assertEquals(ctx);

	return value;
}

/**
 * Set a global variable.
 *
 * @param ctx the context
 * @param name the name of the global variable
 * @param type the value to set
 */
template <typename Type>
inline void setGlobal(ContextPtr ctx, const std::string &name, Type&& type)
{
	assertBegin(ctx);
	push(ctx, std::forward<Type>(type));
	duk_put_global_string(ctx, name.c_str());
	assertEquals(ctx);
}

/* ------------------------------------------------------------------
 * Exception handling
 * ------------------------------------------------------------------ */

/**
 * @class ExceptionAbstract
 * @brief Base class for standard ECMAScript exceptions
 * @warning Override the function create for your own exceptions
 */
class ExceptionAbstract {
protected:
	std::string m_name;
	std::string m_message;

public:
	/**
	 * Construct an exception of type name with the specified message.
	 *
	 * @param name the name (e.g TypeError)
	 * @param message the message
	 */
	ExceptionAbstract(std::string name, std::string message);

	/**
	 * Get the exception type name.
	 *
	 * @return the exception type
	 */
	const std::string &name() const noexcept;

	/**
	 * Create the exception on the stack.
	 *
	 * @note the default implementation search for the global variables
	 * @param ctx the context
	 */
	virtual void create(ContextPtr ctx) const noexcept;
};

/**
 * @class Error
 * @brief Base ECMAScript error class
 */
class Error : public ExceptionAbstract {
public:
	Error(std::string message);
};

/**
 * @class EvalError
 * @brief Error in eval() function
 */
class EvalError : public ExceptionAbstract {
public:
	EvalError(std::string message);
};

/**
 * @class RangeError
 * @brief Value is out of range
 */
class RangeError : public ExceptionAbstract {
public:
	RangeError(std::string message);
};

/**
 * @class ReferenceError
 * @brief Trying to use a variable that does not exist
 */
class ReferenceError : public ExceptionAbstract {
public:
	ReferenceError(std::string message);
};

/**
 * @class SyntaxError
 * @brief Syntax error in the script
 */
class SyntaxError : public ExceptionAbstract {
public:
	SyntaxError(std::string message);
};

/**
 * @class TypeError
 * @brief Invalid type given
 */
class TypeError : public ExceptionAbstract {
public:
	TypeError(std::string message);
};

/**
 * @class URIError
 * @brief URI manipulation failure
 */
class URIError : public ExceptionAbstract {
public:
	URIError(std::string message);
};

/**
 * Throw an ECMAScript exception.
 *
 * @param ctx the context
 * @param ex the exception
 */
template <typename Exception>
void raise(ContextPtr ctx, const Exception &ex)
{
	ex.create(ctx);

	duk_push_string(ctx, ex.name().c_str());
	duk_put_prop_string(ctx, -2, "name");
	duk_throw(ctx);
}

} // !js

#endif // !_JS_H_