changeset 400:c118df15d354

Js: - Add Context::is, - Add more documentation, - Add Null, Undefined tags.
author David Demelier <markand@malikania.fr>
date Thu, 01 Oct 2015 21:30:57 +0200
parents 73517fd307dd
children ca5e4360f79a
files C++/modules/Js/Js.h C++/tests/Js/main.cpp
diffstat 2 files changed, 349 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Js/Js.h	Thu Oct 01 14:02:28 2015 +0200
+++ b/C++/modules/Js/Js.h	Thu Oct 01 21:30:57 2015 +0200
@@ -49,28 +49,42 @@
 
 /**
  * @class Object
- * @brief Empty class tag for push() function
+ * @brief Empty class tag for push() function.
  */
 class Object {
 };
 
 /**
  * @class Array
- * @brief Empty class tag for push() function
+ * @brief Empty class tag for push() function.
  */
 class Array {
 };
 
 /**
  * @class Global
- * @brief Empty class tag to push the global object
+ * @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 Function
- * @brief Duktape/C function definition
+ * @brief Duktape/C function definition.
  */
 class Function {
 public:
@@ -92,7 +106,7 @@
 
 /**
  * @class ErrorInfo
- * @brief Error description
+ * @brief Error description.
  *
  * This class fills the fields got in an Error object.
  */
@@ -117,7 +131,24 @@
 
 /**
  * @class TypeInfo
- * @brief Type information to implement new types in JavaScript's context
+ * @brief Type information to implement new types in JavaScript's context.
+ *
+ * This class depending on your needs may have the following functions:
+ *
+ * - static void push(Context &ctx, Type value)
+ * - static T get(Context &ctx, int index)
+ * - static bool is(Context &ctx, int index)
+ * - static void construct(Context &ctx, Type value)
+ *
+ * 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 `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 `construct` function is used in Context::construct to build a new value as this (e.g. constructors).
  */
 template <typename Type>
 class TypeInfo {
@@ -125,7 +156,9 @@
 
 /**
  * @class File
- * @brief Evaluate script from file
+ * @brief Evaluate script from file.
+ * @see Context::eval
+ * @see Context::peval
  */
 class File {
 public:
@@ -144,7 +177,9 @@
 
 /**
  * @class Script
- * @brief Evaluate script from raw text
+ * @brief Evaluate script from raw text.
+ * @see Context::eval
+ * @see Context::peval
  */
 class Script {
 public:
@@ -163,7 +198,7 @@
 
 /**
  * @class Context
- * @brief RAII based Duktape handler
+ * @brief RAII based Duktape handler.
  *
  * This class is implicitly convertible to duk_context for convenience.
  */
@@ -220,7 +255,7 @@
 	}
 
 	/* ----------------------------------------------------------
-	 * Push / Get / Require
+	 * Push / Get / Require / Is
 	 * ---------------------------------------------------------- */
 
 	/**
@@ -258,6 +293,20 @@
 		return TypeInfo<Type>::require(*this, index);
 	}
 
+	/**
+	 * Check if a value is a type of T.
+	 *
+	 * The TypeInfo<T> must have `static bool is(ContextPtr ptr, int index)`.
+	 *
+	 * @param index the value index
+	 * @return true if
+	 */
+	template <typename T>
+	inline bool is(int index)
+	{
+		return TypeInfo<T>::is(*this, index);
+	}
+
 	// TODO: add optional
 
 	/* --------------------------------------------------------
@@ -276,7 +325,7 @@
 	inline auto getProperty(int index, const std::string &name, std::enable_if_t<!std::is_void<Type>::value> * = nullptr) -> decltype(get<Type>(0))
 	{
 		duk_get_prop_string(m_handle.get(), index, name.c_str());
-		auto &&value = get<Type>(-1);
+		decltype(get<Type>(0)) value = get<Type>(-1);
 		duk_pop(m_handle.get());
 
 		return value;
@@ -316,6 +365,18 @@
 		duk_put_prop_string(m_handle.get(), index, name.c_str());
 	}
 
+	/**
+	 * 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(int index, const std::string &name)
+	{
+		duk_put_prop_string(m_handle.get(), index, name.c_str());
+	}
+
+
 	/* --------------------------------------------------------
 	 * Basic functions
 	 * -------------------------------------------------------- */
@@ -476,6 +537,53 @@
 		duk_pop(m_handle.get());
 	}
 
+	/**
+	 * Return the this binding of the current function.
+	 *
+	 * @return the this binding as the template given
+	 */
+	template <typename T>
+	inline auto self() -> decltype(TypeInfo<T>::get(*this, 0))
+	{
+		duk_push_this(m_handle.get());
+		decltype(TypeInfo<T>::get(*this, 0)) value = TypeInfo<T>::get(*this, -1);
+		duk_pop(m_handle.get());
+
+		return value;
+	}
+
+	/**
+	 * Throw an ECMAScript exception.
+	 *
+	 * @param ctx the context
+	 * @param ex the exception
+	 */
+	template <typename Exception>
+	void raise(const Exception &ex)
+	{
+		ex.create(m_handle.get());
+
+		duk_push_string(m_handle.get(), ex.name().c_str());
+		duk_put_prop_string(m_handle.get(), -2, "name");
+		duk_throw(m_handle.get());
+	}
+
+	/**
+	 * Construct the object in place, setting value as this binding.
+	 *
+	 * The TypeInfo<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(T &&value)
+	{
+		TypeInfo<std::decay_t<T>>::construct(*this, std::forward<T>(value));
+	}
+
 	/* --------------------------------------------------------
 	 * Global functions
 	 * -------------------------------------------------------- */
@@ -491,7 +599,7 @@
 	inline auto getGlobal(const std::string &name, std::enable_if_t<!std::is_void<Type>::value> * = nullptr) -> decltype(get<Type>(0))
 	{
 		duk_get_global_string(m_handle.get(), name.c_str());
-		auto &&value = get<Type>(-1);
+		decltype(get<Type>(0)) value = get<Type>(-1);
 		duk_pop(m_handle.get());
 
 		return value;
@@ -514,57 +622,20 @@
 	 * @param type the value to set
 	 */
 	template <typename Type>
-	inline void setGlobal(const std::string &name, Type&& type)
+	inline void putGlobal(const std::string &name, Type&& type)
 	{
 		push(std::forward<Type>(type));
 		duk_put_global_string(m_handle.get(), name.c_str());
 	}
 
 	/**
-	 * Throw an ECMAScript exception.
+	 * Put the value at the top of the stack as global property.
 	 *
-	 * @param ctx the context
-	 * @param ex the exception
-	 */
-	template <typename Exception>
-	void raise(const Exception &ex)
-	{
-		ex.create(m_handle.get());
-
-		duk_push_string(m_handle.get(), ex.name().c_str());
-		duk_put_prop_string(m_handle.get(), -2, "name");
-		duk_throw(m_handle.get());
-	}
-
-	/**
-	 * Return the this binding of the current function.
-	 *
-	 * @return the this binding as the template given
+	 * @param name the property name
 	 */
-	template <typename T>
-	inline auto self() -> decltype(TypeInfo<T>::get(*this, 0))
+	inline void putGlobal(const std::string &name)
 	{
-		duk_push_this(m_handle.get());
-		auto &&value = TypeInfo<T>::get(*this, -1);
-		duk_pop(m_handle.get());
-
-		return value;
-	}
-
-	/**
-	 * Construct the object in place, setting value as this binding.
-	 *
-	 * The TypeInfo<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(T &&value)
-	{
-		TypeInfo<std::remove_reference_t<T>>::construct(*this, std::forward<T>(value));
+		duk_put_global_string(m_handle.get(), name.c_str());
 	}
 };
 
@@ -574,7 +645,7 @@
 
 /**
  * @class ExceptionAbstract
- * @brief Base class for standard ECMAScript exceptions
+ * @brief Base class for standard ECMAScript exceptions.
  * @warning Override the function create for your own exceptions
  */
 class ExceptionAbstract {
@@ -621,7 +692,7 @@
 
 /**
  * @class Error
- * @brief Base ECMAScript error class
+ * @brief Base ECMAScript error class.
  */
 class Error : public ExceptionAbstract {
 public:
@@ -633,7 +704,7 @@
 
 /**
  * @class EvalError
- * @brief Error in eval() function
+ * @brief Error in eval() function.
  */
 class EvalError : public ExceptionAbstract {
 public:
@@ -645,7 +716,7 @@
 
 /**
  * @class RangeError
- * @brief Value is out of range
+ * @brief Value is out of range.
  */
 class RangeError : public ExceptionAbstract {
 public:
@@ -657,7 +728,7 @@
 
 /**
  * @class ReferenceError
- * @brief Trying to use a variable that does not exist
+ * @brief Trying to use a variable that does not exist.
  */
 class ReferenceError : public ExceptionAbstract {
 public:
@@ -669,7 +740,7 @@
 
 /**
  * @class SyntaxError
- * @brief Syntax error in the script
+ * @brief Syntax error in the script.
  */
 class SyntaxError : public ExceptionAbstract {
 public:
@@ -681,7 +752,7 @@
 
 /**
  * @class TypeError
- * @brief Invalid type given
+ * @brief Invalid type given.
  */
 class TypeError : public ExceptionAbstract {
 public:
@@ -693,7 +764,7 @@
 
 /**
  * @class URIError
- * @brief URI manipulation failure
+ * @brief URI manipulation failure.
  */
 class URIError : public ExceptionAbstract {
 public:
@@ -709,19 +780,24 @@
 
 /**
  * @class TypeInfo<int>
- * @brief Default implementation for int
+ * @brief Default implementation for int.
  *
- * Provides: get, push, require, optional
+ * Provides: is, get, push.
  */
 template <>
 class TypeInfo<int> {
 public:
-	static int get(ContextPtr ctx, int index)
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_number(ctx, index);
+	}
+
+	static inline int get(Context &ctx, int index)
 	{
 		return duk_get_int(ctx, index);
 	}
 
-	static void push(ContextPtr ctx, int value)
+	static inline void push(Context &ctx, int value)
 	{
 		duk_push_int(ctx, value);
 	}
@@ -729,19 +805,24 @@
 
 /**
  * @class TypeInfo<bool>
- * @brief Default implementation for bool
+ * @brief Default implementation for bool.
  *
- * Provides: get, push, require, optional
+ * Provides: is, get, push
  */
 template <>
 class TypeInfo<bool> {
 public:
-	static bool get(Context &ctx, int index)
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_boolean(ctx, index);
+	}
+
+	static inline bool get(Context &ctx, int index)
 	{
 		return duk_get_boolean(ctx, index);
 	}
 
-	static void push(Context &ctx, bool value)
+	static inline void push(Context &ctx, bool value)
 	{
 		duk_push_boolean(ctx, value);
 	}
@@ -749,19 +830,24 @@
 
 /**
  * @class TypeInfo<double>
- * @brief Default implementation for double
+ * @brief Default implementation for double.
  *
- * Provides: get, push, require, optional
+ * Provides: is, get, push
  */
 template <>
 class TypeInfo<double> {
 public:
-	static double get(Context &ctx, int index)
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_number(ctx, index);
+	}
+
+	static inline double get(Context &ctx, int index)
 	{
 		return duk_get_number(ctx, index);
 	}
 
-	static void push(Context &ctx, double value)
+	static inline void push(Context &ctx, double value)
 	{
 		duk_push_number(ctx, value);
 	}
@@ -769,16 +855,21 @@
 
 /**
  * @class TypeInfo<std::string>
- * @brief Default implementation for std::string
+ * @brief Default implementation for std::string.
  *
- * Provides: get, push, require, optional
+ * Provides: is, get, push.
  *
  * Note: the functions allows embedded '\0'.
  */
 template <>
 class TypeInfo<std::string> {
 public:
-	static std::string get(Context &ctx, int index)
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_string(ctx, index);
+	}
+
+	static inline std::string get(Context &ctx, int index)
 	{
 		duk_size_t size;
 		const char *text = duk_get_lstring(ctx, index, &size);
@@ -786,7 +877,7 @@
 		return std::string{text, size};
 	}
 
-	static void push(Context &ctx, const std::string &value)
+	static inline void push(Context &ctx, const std::string &value)
 	{
 		duk_push_lstring(ctx, value.c_str(), value.length());
 	}
@@ -794,14 +885,24 @@
 
 /**
  * @class TypeInfo<const char *>
- * @brief Default implementation for const char literals
+ * @brief Default implementation for const char literals.
  *
- * Provides: push
+ * Provides: is, get, push
  */
 template <>
 class TypeInfo<const char *> {
 public:
-	static void push(Context &ctx, const char *value)
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_string(ctx, index);
+	}
+
+	static inline const char *get(Context &ctx, int index)
+	{
+		return duk_get_string(ctx, index);
+	}
+
+	static inline void push(Context &ctx, const char *value)
 	{
 		duk_push_string(ctx, value);
 	}
@@ -809,7 +910,7 @@
 
 /**
  * @class TypeInfo<Function>
- * @brief Default implementation for Function
+ * @brief Push C++ function to the stack.
  *
  * Provides: push
  *
@@ -862,10 +963,16 @@
 	}
 };
 
+/**
+ * @class TypeInfo<FunctionMap>
+ * @brief Put the functions to the object at the top of the stack.
+ *
+ * Provides: push
+ */
 template <>
 class TypeInfo<FunctionMap> {
 public:
-	static void push(Context &ctx, const FunctionMap &map)
+	static inline void push(Context &ctx, const FunctionMap &map)
 	{
 		for (const auto &entry : map)
 			ctx.putProperty(-1, entry.first, entry.second);
@@ -874,16 +981,19 @@
 
 /**
  * @class TypeInfo<Object>
- * @brief Default implementation for Object
+ * @brief Push empty object to the stack.
  *
- * Provides: push
- *
- * This implementation is provided to push empty object into the stack.
+ * Provides: is, push
  */
 template <>
 class TypeInfo<Object> {
 public:
-	static void push(Context &ctx, const Object &)
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_object(ctx, index);
+	}
+
+	static inline void push(Context &ctx, const Object &)
 	{
 		duk_push_object(ctx);
 	}
@@ -891,25 +1001,74 @@
 
 /**
  * @class TypeInfo<Array>
- * @brief Default implementation for Array
+ * @brief Push empty array to the stack.
  *
- * Provides: push
- *
- * This implementation is provided to push empty array into the stack.
+ * Provides: is, push
  */
 template <>
 class TypeInfo<Array> {
 public:
-	static void push(Context &ctx, const Array &)
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_array(ctx, index);
+	}
+
+	static inline void push(Context &ctx, const Array &)
 	{
 		duk_push_array(ctx);
 	}
 };
 
+/**
+ * @class TypeInfo<Undefined>
+ * @brief Push undefined value to the stack.
+ *
+ * Provides: is, push,
+ */
+template <>
+class TypeInfo<Undefined> {
+public:
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_undefined(ctx, index);
+	}
+
+	static inline void push(Context &ctx, const Undefined &)
+	{
+		duk_push_undefined(ctx);
+	}
+};
+
+/**
+ * @class TypeInfo<Null>
+ * @brief Push null value to the stack.
+ *
+ * Provides: is, push,
+ */
+template <>
+class TypeInfo<Null> {
+public:
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_null(ctx, index);
+	}
+
+	static inline void push(Context &ctx, const Null &)
+	{
+		duk_push_null(ctx);
+	}
+};
+
+/**
+ * @class TypeInfo<Global>
+ * @brief Push the global object to the stack.
+ *
+ * Provides: push
+ */
 template <>
 class TypeInfo<Global> {
 public:
-	static void push(Context &ctx, const Global &)
+	static inline void push(Context &ctx, const Global &)
 	{
 		duk_push_global_object(ctx);
 	}
@@ -921,15 +1080,9 @@
 
 /**
  * @class TypeInfoShared
- * @brief Generates push / get / require / optional / construct for std::shared_ptr
- *
- * Specialize TypeInfo<std::shared_ptr<T>> and inherits from this class to implement the push(),
- * get(), require() and optional() functions.
+ * @brief Push objects as shared_ptr.
  *
- * You only need to implement `static void prototype(Context &ctx)` which must push the prototype
- * to use for the underlying object.
- *
- * This class can be used to both push and construct shared_ptr objects.
+ * TODO: add better documentation when finalized.
  */
 template <typename T>
 class TypeInfoShared {
@@ -1001,16 +1154,9 @@
 
 /**
  * @class TypeInfoPointer
- * @brief Generates push / get / require / optional / construct for custom pointer object
- *
- * Specialize TypeInfo<T *> and inherits from this class to implement the push(),
- * get(), require() and optional() functions.
+ * @brief Push pointers as objects.
  *
- * You only need to implement `static void prototype(Context &ctx)` which must push the prototype
- * to use for the underlying object.
- *
- * This class can be used to both push and construct pointers objects and is useful when you want to push C++
- * objects that will be only needed into JavaScript side.
+ * TODO: add better documentation when finalized.
  */
 template <typename T>
 class TypeInfoPointer {
--- a/C++/tests/Js/main.cpp	Thu Oct 01 14:02:28 2015 +0200
+++ b/C++/tests/Js/main.cpp	Thu Oct 01 21:30:57 2015 +0200
@@ -64,6 +64,98 @@
 	ASSERT_EQ("hello world!", context.get<std::string>(-1));
 }
 
+TEST(PushAndGet, undefined)
+{
+	Context context;
+
+	context.push(Undefined{});
+	ASSERT_EQ(DUK_TYPE_UNDEFINED, context.type(-1));
+}
+
+TEST(PushAndGet, null)
+{
+	Context context;
+
+	context.push(Null{});
+	ASSERT_EQ(DUK_TYPE_NULL, context.type(-1));
+}
+
+/* --------------------------------------------------------
+ * Is
+ * -------------------------------------------------------- */
+
+TEST(Is, boolean)
+{
+	Context context;
+
+	context.push(true);
+	ASSERT_TRUE(context.is<bool>(-1));
+}
+
+TEST(Is, integer)
+{
+	Context context;
+
+	context.push(123);
+	ASSERT_TRUE(context.is<int>(-1));
+}
+
+TEST(Is, number)
+{
+	Context context;
+
+	context.push(50.5);
+	ASSERT_TRUE(context.is<double>(-1));
+}
+
+TEST(Is, string)
+{
+	Context context;
+
+	context.push(std::string{"hello"});
+	ASSERT_TRUE(context.is<std::string>(-1));
+}
+
+TEST(Is, cstring)
+{
+	Context context;
+
+	context.push("hello");
+	ASSERT_TRUE(context.is<const char *>(-1));
+}
+
+TEST(Is, undefined)
+{
+	Context context;
+
+	context.push(Undefined{});
+	ASSERT_TRUE(context.is<Undefined>(-1));
+}
+
+TEST(Is, null)
+{
+	Context context;
+
+	context.push(Null{});
+	ASSERT_TRUE(context.is<Null>(-1));
+}
+
+TEST(Is, object)
+{
+	Context context;
+
+	context.push(Object{});
+	ASSERT_TRUE(context.is<Object>(-1));
+}
+
+TEST(Is, array)
+{
+	Context context;
+
+	context.push(Array{});
+	ASSERT_TRUE(context.is<Array>(-1));
+}
+
 /* --------------------------------------------------------
  * Basics
  * -------------------------------------------------------- */