Mercurial > code
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 * -------------------------------------------------------- */