changeset 402:c21f37c9cd4a

Js: - Add Context::optional - Add Context::require
author David Demelier <markand@malikania.fr>
date Mon, 05 Oct 2015 13:22:20 +0200
parents ca5e4360f79a
children d5ec1174b707
files C++/modules/Js/Js.cpp C++/modules/Js/Js.h C++/tests/Js/main.cpp
diffstat 3 files changed, 341 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/C++/modules/Js/Js.cpp	Sat Oct 03 11:27:49 2015 +0200
+++ b/C++/modules/Js/Js.cpp	Mon Oct 05 13:22:20 2015 +0200
@@ -1,5 +1,5 @@
 /*
- * Js.cpp -- JavaScript wrapper for Duktape
+ * Js.cpp -- JavaScript C++14 wrapper for Duktape
  *
  * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr>
  *
--- a/C++/modules/Js/Js.h	Sat Oct 03 11:27:49 2015 +0200
+++ b/C++/modules/Js/Js.h	Mon Oct 05 13:22:20 2015 +0200
@@ -1,5 +1,5 @@
 /*
- * Js.h -- JavaScript wrapper for Duktape
+ * Js.h -- JavaScript C++14 wrapper for Duktape
  *
  * Copyright (c) 2013-2015 David Demelier <markand@malikania.fr>
  *
@@ -29,12 +29,12 @@
  * For convenience, this file also provides templated functions, overloads and much more.
  */
 
-#include <cassert>
 #include <functional>
 #include <memory>
 #include <string>
 #include <type_traits>
 #include <unordered_map>
+#include <utility>
 #include <vector>
 
 #include <duktape.h>
@@ -142,20 +142,31 @@
  *
  * 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)
+ * - `static void construct(Context &ctx, Type value)`
+ * - `static Type get(Context &ctx, int index)`
+ * - `static bool is(Context &ctx, int index)`
+ * - `static Type optional(Context &ctx, int index, Type defaultValue)`
+ * - `static void push(Context &ctx, Type value)`
+ * - `static Type require(Context &ctx, int index)`
  *
- * 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 `construct` function is used in Context::construct to build a new value as this (e.g. constructors).
  *
  * 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).
+ * The `optional` function is used in Context::optional to get a value or a replacement if not applicable.
+ *
+ * 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 `require` function is used in Context::require to get a value from the stack or raise a JavaScript exception if
+ * not applicable.
+ *
+ * This class is fully specialized for: `bool`, `const char *`, `double`, `int`, `std::string`.
+ *
+ * It is also partially specialized for : `Global`, `Object`, `Array`, `Undefined`, `Null`, `std::vector<Type>`.
  */
 template <typename Type>
 class TypeInfo {
@@ -262,7 +273,7 @@
 	}
 
 	/* ----------------------------------------------------------
-	 * Push / Get / Require / Is
+	 * Push / Get / Require / Is / Optional
 	 * ---------------------------------------------------------- */
 
 	/**
@@ -306,7 +317,7 @@
 	 * The TypeInfo<T> must have `static bool is(ContextPtr ptr, int index)`.
 	 *
 	 * @param index the value index
-	 * @return true if
+	 * @return true if is the type
 	 */
 	template <typename T>
 	inline bool is(int index)
@@ -314,7 +325,21 @@
 		return TypeInfo<T>::is(*this, index);
 	}
 
-	// TODO: add optional
+	/**
+	 * Get an optional value from the stack, if the value is not available of not the correct type,
+	 * return defaultValue instead.
+	 *
+	 * The TypeInfo<T> must have `static T optional(Context &, int index, T &&defaultValue)`.
+	 *
+	 * @param index the value index
+	 * @param defaultValue the value replacement
+	 * @return the value or defaultValue
+	 */
+	template <typename Type>
+	inline auto optional(int index, Type &&defaultValue)
+	{
+		return TypeInfo<std::decay_t<Type>>::optional(*this, index, std::forward<Type>(defaultValue));
+	}
 
 	/* --------------------------------------------------------
 	 * Get properties (for objects and arrays)
@@ -328,8 +353,8 @@
 	 * @return the value
 	 * @note The stack is unchanged
 	 */
-	template <typename Type>
-	inline auto getProperty(int index, const std::string &name, std::enable_if_t<!std::is_void<Type>::value> * = nullptr) -> decltype(get<Type>(0))
+	template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr>
+	inline auto getProperty(int index, const std::string &name) -> decltype(get<Type>(0))
 	{
 		duk_get_prop_string(m_handle.get(), index, name.c_str());
 		decltype(get<Type>(0)) value = get<Type>(-1);
@@ -338,8 +363,16 @@
 		return value;
 	}
 
-	template <typename Type>
-	inline auto getProperty(int index, int position, std::enable_if_t<!std::is_void<Type>::value> * = nullptr) -> decltype(get<Type>(0))
+	/**
+	 * Get a property by index, for arrays.
+	 *
+	 * @param index the object index
+	 * @param position the position int the object
+	 * @return the value
+	 * @note The stack is unchanged
+	 */
+	template <typename Type, typename std::enable_if_t<!std::is_void<Type>::value> * = nullptr>
+	inline auto getProperty(int index, int position) -> decltype(get<Type>(0))
 	{
 		duk_get_prop_index(m_handle.get(), index, position);
 		decltype(get<Type>(0)) value = get<Type>(-1);
@@ -355,14 +388,21 @@
 	 * @param name the property name
 	 * @note The stack contains the property value
 	 */
-	template <typename Type>
-	inline void getProperty(int index, const std::string &name, std::enable_if_t<std::is_void<Type>::value> * = nullptr)
+	template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr>
+	inline void getProperty(int index, const std::string &name)
 	{
 		duk_get_prop_string(m_handle.get(), index, name.c_str());
 	}
 
-	template <typename Type>
-	inline void getProperty(int index, int position, std::enable_if_t<std::is_void<Type>::value> * = nullptr)
+	/**
+	 * Get the property by index and push it to the stack from the object at the specified index.
+	 *
+	 * @param index the object index
+	 * @param position the position in the object
+	 * @note The stack contains the property value
+	 */
+	template <typename Type, typename std::enable_if_t<std::is_void<Type>::value> * = nullptr>
+	inline void getProperty(int index, int position)
 	{
 		duk_get_prop_index(m_handle.get(), index, position);
 	}
@@ -388,6 +428,14 @@
 		duk_put_prop_string(m_handle.get(), index, name.c_str());
 	}
 
+	/**
+	 * Set a property by index, for arrays.
+	 *
+	 * @param index the object index
+	 * @param position the position in the object
+	 * @param value the value to forward
+	 * @note The stack is unchanged
+	 */
 	template <typename Type>
 	void putProperty(int index, int position, Type &&value)
 	{
@@ -408,6 +456,12 @@
 		duk_put_prop_string(m_handle.get(), index, name.c_str());
 	}
 
+	/**
+	 * Put the value that is at the top of the stack to the object as index.
+	 *
+	 * @param index the object index
+	 * @param position the position in the object
+	 */
 	inline void putProperty(int index, int position)
 	{
 		duk_put_prop_index(m_handle.get(), index, position);
@@ -432,7 +486,7 @@
 	 * @param index the idnex
 	 * @return the type
 	 */
-	int type(int index) noexcept
+	inline int type(int index) noexcept
 	{
 		return duk_get_type(m_handle.get(), index);
 	}
@@ -686,8 +740,8 @@
  */
 class ExceptionAbstract {
 protected:
-	std::string m_name;
-	std::string m_message;
+	std::string m_name;	//!< Name of exception (e.g RangeError)
+	std::string m_message;	//!< The message
 
 public:
 	/**
@@ -733,7 +787,7 @@
 class Error : public ExceptionAbstract {
 public:
 	inline Error(std::string message) noexcept
-		: ExceptionAbstract("Error", std::move(message))
+		: ExceptionAbstract{"Error", std::move(message)}
 	{
 	}
 };
@@ -745,7 +799,7 @@
 class EvalError : public ExceptionAbstract {
 public:
 	inline EvalError(std::string message) noexcept
-		: ExceptionAbstract("EvalError", std::move(message))
+		: ExceptionAbstract{"EvalError", std::move(message)}
 	{
 	}
 };
@@ -757,7 +811,7 @@
 class RangeError : public ExceptionAbstract {
 public:
 	inline RangeError(std::string message) noexcept
-		: ExceptionAbstract("RangeError", std::move(message))
+		: ExceptionAbstract{"RangeError", std::move(message)}
 	{
 	}
 };
@@ -769,7 +823,7 @@
 class ReferenceError : public ExceptionAbstract {
 public:
 	inline ReferenceError(std::string message) noexcept
-		: ExceptionAbstract("ReferenceError", std::move(message))
+		: ExceptionAbstract{"ReferenceError", std::move(message)}
 	{
 	}
 };
@@ -781,7 +835,7 @@
 class SyntaxError : public ExceptionAbstract {
 public:
 	inline SyntaxError(std::string message) noexcept
-		: ExceptionAbstract("SyntaxError", std::move(message))
+		: ExceptionAbstract{"SyntaxError", std::move(message)}
 	{
 	}
 };
@@ -793,7 +847,7 @@
 class TypeError : public ExceptionAbstract {
 public:
 	inline TypeError(std::string message) noexcept
-		: ExceptionAbstract("TypeError", std::move(message))
+		: ExceptionAbstract{"TypeError", std::move(message)}
 	{
 	}
 };
@@ -805,31 +859,40 @@
 class URIError : public ExceptionAbstract {
 public:
 	inline URIError(std::string message) noexcept
-		: ExceptionAbstract("URIError", std::move(message))
+		: ExceptionAbstract{"URIError", std::move(message)}
 	{
 	}
 };
 
 /* ------------------------------------------------------------------
- * Standard overloads for TypeInfo<T>::get
+ * Standard overloads for TypeInfo<T>
  * ------------------------------------------------------------------ */
 
 /**
  * @class TypeInfo<int>
  * @brief Default implementation for int.
  *
- * Provides: is, get, push.
+ * Provides: get, is, optional, push, require.
  */
 template <>
 class TypeInfo<int> {
 public:
+	static inline int get(Context &ctx, int index)
+	{
+		return duk_get_int(ctx, index);
+	}
+
 	static inline bool is(Context &ctx, int index)
 	{
 		return duk_is_number(ctx, index);
 	}
 
-	static inline int get(Context &ctx, int index)
+	static inline int optional(Context &ctx, int index, int defaultValue)
 	{
+		if (!duk_is_number(ctx, index)) {
+			return defaultValue;
+		}
+
 		return duk_get_int(ctx, index);
 	}
 
@@ -837,24 +900,38 @@
 	{
 		duk_push_int(ctx, value);
 	}
+
+	static inline int require(Context &ctx, int index)
+	{
+		return duk_require_int(ctx, index);
+	}
 };
 
 /**
  * @class TypeInfo<bool>
  * @brief Default implementation for bool.
  *
- * Provides: is, get, push
+ * Provides: get, is, optional, push, require.
  */
 template <>
 class TypeInfo<bool> {
 public:
+	static inline bool get(Context &ctx, int index)
+	{
+		return duk_get_boolean(ctx, index);
+	}
+
 	static inline bool is(Context &ctx, int index)
 	{
 		return duk_is_boolean(ctx, index);
 	}
 
-	static inline bool get(Context &ctx, int index)
+	static inline bool optional(Context &ctx, int index, bool defaultValue)
 	{
+		if (!duk_is_boolean(ctx, index)) {
+			return defaultValue;
+		}
+
 		return duk_get_boolean(ctx, index);
 	}
 
@@ -862,24 +939,38 @@
 	{
 		duk_push_boolean(ctx, value);
 	}
+
+	static inline bool require(Context &ctx, int index)
+	{
+		return duk_require_boolean(ctx, index);
+	}
 };
 
 /**
  * @class TypeInfo<double>
  * @brief Default implementation for double.
  *
- * Provides: is, get, push
+ * Provides: get, is, optional, push, require.
  */
 template <>
 class TypeInfo<double> {
 public:
+	static inline double get(Context &ctx, int index)
+	{
+		return duk_get_number(ctx, index);
+	}
+
 	static inline bool is(Context &ctx, int index)
 	{
 		return duk_is_number(ctx, index);
 	}
 
-	static inline double get(Context &ctx, int index)
+	static inline double optional(Context &ctx, int index, double defaultValue)
 	{
+		if (!duk_is_number(ctx, index)) {
+			return defaultValue;
+		}
+
 		return duk_get_number(ctx, index);
 	}
 
@@ -887,24 +978,24 @@
 	{
 		duk_push_number(ctx, value);
 	}
+
+	static inline double require(Context &ctx, int index)
+	{
+		return duk_require_number(ctx, index);
+	}
 };
 
 /**
  * @class TypeInfo<std::string>
  * @brief Default implementation for std::string.
  *
- * Provides: is, get, push.
+ * Provides: get, is, optional, push, require.
  *
  * Note: the functions allows embedded '\0'.
  */
 template <>
 class TypeInfo<std::string> {
 public:
-	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;
@@ -913,28 +1004,59 @@
 		return std::string{text, size};
 	}
 
+	static inline bool is(Context &ctx, int index)
+	{
+		return duk_is_string(ctx, index);
+	}
+
+	static inline std::string optional(Context &ctx, int index, std::string defaultValue)
+	{
+		if (!duk_is_string(ctx, index)) {
+			return defaultValue;
+		}
+
+		return get(ctx, index);
+	}
+
 	static inline void push(Context &ctx, const std::string &value)
 	{
 		duk_push_lstring(ctx, value.c_str(), value.length());
 	}
+
+	static inline std::string require(Context &ctx, int index)
+	{
+		duk_size_t size;
+		const char *text = duk_require_lstring(ctx, index, &size);
+
+		return std::string{text, size};
+	}
 };
 
 /**
  * @class TypeInfo<const char *>
  * @brief Default implementation for const char literals.
  *
- * Provides: is, get, push
+ * Provides: get, is, optional, push, require.
  */
 template <>
 class TypeInfo<const char *> {
 public:
+	static inline const char *get(Context &ctx, int index)
+	{
+		return duk_get_string(ctx, index);
+	}
+
 	static inline bool is(Context &ctx, int index)
 	{
 		return duk_is_string(ctx, index);
 	}
 
-	static inline const char *get(Context &ctx, int index)
+	static inline const char *optional(Context &ctx, int index, const char *defaultValue)
 	{
+		if (!duk_is_string(ctx, index)) {
+			return defaultValue;
+		}
+
 		return duk_get_string(ctx, index);
 	}
 
@@ -942,13 +1064,18 @@
 	{
 		duk_push_string(ctx, value);
 	}
+
+	static inline const char *require(Context &ctx, int index)
+	{
+		return duk_require_string(ctx, index);
+	}
 };
 
 /**
  * @class TypeInfo<Function>
  * @brief Push C++ function to the stack.
  *
- * Provides: push
+ * Provides: push.
  *
  * This implementation push a Duktape/C function that is wrapped as C++ for convenience.
  */
@@ -957,8 +1084,6 @@
 public:
 	static void push(Context &ctx, Function fn)
 	{
-		assert(fn.function);
-
 		/* 1. Push function wrapper */
 		duk_push_c_function(ctx, [] (duk_context *ctx) -> duk_ret_t {
 			Context context{ctx};
@@ -1003,15 +1128,16 @@
  * @class TypeInfo<FunctionMap>
  * @brief Put the functions to the object at the top of the stack.
  *
- * Provides: push
+ * Provides: push.
  */
 template <>
 class TypeInfo<FunctionMap> {
 public:
 	static inline void push(Context &ctx, const FunctionMap &map)
 	{
-		for (const auto &entry : map)
+		for (const auto &entry : map) {
 			ctx.putProperty(-1, entry.first, entry.second);
+		}
 	}
 };
 
@@ -1019,7 +1145,7 @@
  * @class TypeInfo<Object>
  * @brief Push empty object to the stack.
  *
- * Provides: is, push
+ * Provides: is, push.
  */
 template <>
 class TypeInfo<Object> {
@@ -1039,7 +1165,7 @@
  * @class TypeInfo<Array>
  * @brief Push empty array to the stack.
  *
- * Provides: is, push
+ * Provides: is, push.
  */
 template <>
 class TypeInfo<Array> {
@@ -1059,7 +1185,7 @@
  * @class TypeInfo<Undefined>
  * @brief Push undefined value to the stack.
  *
- * Provides: is, push,
+ * Provides: is, push.
  */
 template <>
 class TypeInfo<Undefined> {
@@ -1079,7 +1205,7 @@
  * @class TypeInfo<Null>
  * @brief Push null value to the stack.
  *
- * Provides: is, push,
+ * Provides: is, push.
  */
 template <>
 class TypeInfo<Null> {
@@ -1099,7 +1225,7 @@
  * @class TypeInfo<Global>
  * @brief Push the global object to the stack.
  *
- * Provides: push
+ * Provides: push.
  */
 template <>
 class TypeInfo<Global> {
@@ -1114,7 +1240,7 @@
  * @class TypeInfo<Map<T>>
  * @brief Push a map of key-value pair as objects.
  *
- * Provides: push
+ * Provides: push.
  *
  * This class is convenient for settings constants such as enums, string and such.
  */
@@ -1136,11 +1262,27 @@
  * @class TypeInfo<std::vector<T>>
  * @brief Push or get vectors as JavaScript arrays.
  *
- * Provides: push
+ * Provides: get, push.
  */
 template <typename T>
 class TypeInfo<std::vector<T>> {
 public:
+	static std::vector<T> get(Context &ctx, int index)
+	{
+		std::vector<T> result;
+
+		if (!duk_is_array(ctx, -1)) {
+			return result;
+		}
+
+		int total = duk_get_length(ctx, index);
+		for (int i = 0; i < total; ++i) {
+			result.push_back(ctx.getProperty<T>(index, i));
+		}
+
+		return result;
+	}
+
 	static void push(Context &ctx, const std::vector<T> &array)
 	{
 		duk_push_array(ctx);
@@ -1151,23 +1293,6 @@
 			duk_put_prop_index(ctx, -2, i++);
 		}
 	}
-
-	static std::vector<T> get(Context &ctx, int index)
-	{
-		std::vector<T> result;
-
-		if (!duk_is_array(ctx, -1)) {
-			return result;
-		}
-
-		/* XXX: something else than length property? */
-		int total = ctx.getProperty<int>(index, "length");
-		for (int i = 0; i < total; ++i) {
-			result.push_back(ctx.getProperty<T>(index, i));
-		}
-
-		return result;
-	}
 };
 
 /* ------------------------------------------------------------------
--- a/C++/tests/Js/main.cpp	Sat Oct 03 11:27:49 2015 +0200
+++ b/C++/tests/Js/main.cpp	Mon Oct 05 13:22:20 2015 +0200
@@ -20,6 +20,14 @@
 
 #include <Js.h>
 
+/*
+ * TODO:
+ *
+ * - document stack modification in all functions,
+ * - check that the stack is correct,
+ * - add more C Duktape wrappers.
+ */
+
 using namespace js;
 
 /* --------------------------------------------------------
@@ -111,6 +119,95 @@
 }
 
 /* --------------------------------------------------------
+ * Require
+ * -------------------------------------------------------- */
+
+TEST(Require, boolean)
+{
+	Context context;
+
+	try {
+		context.push(Function{[] (Context &ctx) -> int {
+			ctx.require<bool>(0);
+
+			return 0;
+		}});
+		context.peval();
+
+		FAIL() << "exception expected";
+	} catch (...) {
+	}
+}
+
+TEST(Require, integer)
+{
+	Context context;
+
+	try {
+		context.push(Function{[] (Context &ctx) -> int {
+			ctx.require<int>(0);
+
+			return 0;
+		}});
+		context.peval();
+
+		FAIL() << "exception expected";
+	} catch (...) {
+	}
+}
+
+TEST(Require, number)
+{
+	Context context;
+
+	try {
+		context.push(Function{[] (Context &ctx) -> int {
+			ctx.require<double>(0);
+
+			return 0;
+		}});
+		context.peval();
+
+		FAIL() << "exception expected";
+	} catch (...) {
+	}
+}
+
+TEST(Require, string)
+{
+	Context context;
+
+	try {
+		context.push(Function{[] (Context &ctx) -> int {
+			ctx.require<std::string>(0);
+
+			return 0;
+		}});
+		context.peval();
+
+		FAIL() << "exception expected";
+	} catch (...) {
+	}
+}
+
+TEST(Require, cstring)
+{
+	Context context;
+
+	try {
+		context.push(Function{[] (Context &ctx) -> int {
+			ctx.require<const char *>(0);
+
+			return 0;
+		}});
+		context.peval();
+
+		FAIL() << "exception expected";
+	} catch (...) {
+	}
+}
+
+/* --------------------------------------------------------
  * Is
  * -------------------------------------------------------- */
 
@@ -187,6 +284,50 @@
 }
 
 /* --------------------------------------------------------
+ * Optional
+ * -------------------------------------------------------- */
+
+TEST(Optional, boolean)
+{
+	Context context;
+
+	ASSERT_TRUE(context.optional<bool>(0, true));
+	ASSERT_FALSE(context.optional<bool>(0, false));
+}
+
+TEST(Optional, integer)
+{
+	Context context;
+
+	ASSERT_EQ(123, context.optional<int>(0, 123));
+	ASSERT_EQ(456, context.optional<int>(0, 456));
+}
+
+TEST(Optional, number)
+{
+	Context context;
+
+	ASSERT_EQ(10.0, context.optional<double>(0, 10.0));
+	ASSERT_EQ(20.0, context.optional<double>(0, 20.0));
+}
+
+TEST(Optional, string)
+{
+	Context context;
+
+	ASSERT_EQ("no", context.optional<std::string>(0, "no"));
+	ASSERT_EQ("yes", context.optional<std::string>(0, "yes"));
+}
+
+TEST(Optional, cstring)
+{
+	Context context;
+
+	ASSERT_STREQ("no", context.optional<const char *>(0, "no"));
+	ASSERT_STREQ("yes", context.optional<const char *>(0, "yes"));
+}
+
+/* --------------------------------------------------------
  * Basics
  * -------------------------------------------------------- */