From cc9e397f25e9f969e0569a79327754bd2e6b26fa Mon Sep 17 00:00:00 2001 From: alandefreitas Date: Tue, 17 Oct 2023 21:12:46 -0300 Subject: [PATCH] test: clang::mrdox::dom unit tests --- include/mrdox/Dom/Array.hpp | 29 +- include/mrdox/Dom/Array.ipp | 13 +- include/mrdox/Dom/Function.hpp | 19 +- include/mrdox/Dom/Function.ipp | 23 +- include/mrdox/Dom/Kind.hpp | 48 + include/mrdox/Dom/Object.hpp | 217 ++- include/mrdox/Dom/Object.ipp | 261 +-- include/mrdox/Dom/String.hpp | 4 +- include/mrdox/Dom/Value.hpp | 63 +- include/mrdox/Support/Handlebars.hpp | 2 +- src/lib/Dom/Array.cpp | 26 +- src/lib/Dom/Function.cpp | 2 +- src/lib/Dom/Object.cpp | 104 +- src/lib/Dom/Value.cpp | 144 +- src/lib/Support/Handlebars.cpp | 810 ++++---- src/lib/Support/JavaScript.cpp | 83 +- src/lib/Support/Lua.cpp | 17 +- src/test/lib/Dom/Dom.cpp | 2133 ++++++++++++++++++++++ src/test/lib/Support/Handlebars.cpp | 402 ++-- test-files/handlebars/features_test.adoc | 562 +++--- 20 files changed, 3583 insertions(+), 1379 deletions(-) diff --git a/include/mrdox/Dom/Array.hpp b/include/mrdox/Dom/Array.hpp index 2c33874e3..75b9d2786 100644 --- a/include/mrdox/Dom/Array.hpp +++ b/include/mrdox/Dom/Array.hpp @@ -24,7 +24,15 @@ namespace dom { class ArrayImpl; class Value; -/** An array of values. +/** An array of values + + Arrays are a collection of indexed values. They + are an extension of objects with a particular + relationship between integer-keyed properties + and some abstract length-property. Besides, + they include convenient methods to manipulate + these ordered sequences of values. + */ class MRDOX_DECL Array final @@ -40,13 +48,13 @@ class MRDOX_DECL This is a read-only reference to an element. */ - using reference = value_type const&; + using reference = value_type; /** A reference to an element. This is a read-only reference to an element. */ - using const_reference = value_type const&; + using const_reference = value_type; /** A pointer to an element. */ @@ -182,10 +190,6 @@ class MRDOX_DECL */ void set(size_type i, Value v); - /** Return the i-th element, without bounds checking. - */ - value_type operator[](size_type i) const; - /** Return the i-th element. @throw Exception `i >= size()` @@ -217,7 +221,15 @@ class MRDOX_DECL If the array is read-only, an exception is thrown. */ - void emplace_back(value_type value); + void push_back(value_type value); + + /** Append an element to the end of the array. + + If the array is read-only, an exception + is thrown. + */ + template< class... Args > + void emplace_back(Args&&... args); /** Concatenate two arrays. */ @@ -356,6 +368,7 @@ class MRDOX_DECL value_type get(size_type i) const override; void set(size_type i, Value v) override; void emplace_back(value_type value) override; + char const* type_key() const noexcept override; private: std::vector elements_; diff --git a/include/mrdox/Dom/Array.ipp b/include/mrdox/Dom/Array.ipp index 278bab258..bbfb72332 100644 --- a/include/mrdox/Dom/Array.ipp +++ b/include/mrdox/Dom/Array.ipp @@ -175,11 +175,6 @@ inline void Array::set(std::size_t i, Value v) impl_->set(i, std::move(v)); } -inline auto Array::operator[](std::size_t i) const -> value_type -{ - return get(i); -} - inline auto Array::at(std::size_t i) const -> value_type { if(i < size()) @@ -207,11 +202,17 @@ inline auto Array::end() const -> iterator return {*impl_, impl_->size()}; } -inline void Array::emplace_back(value_type value) +inline void Array::push_back(value_type value) { impl_->emplace_back(std::move(value)); } +template< class... Args > +void Array::emplace_back(Args&&... args) +{ + impl_->emplace_back(value_type(std::forward(args)...)); +} + inline Array operator+(Array const& lhs, Array const& rhs) { diff --git a/include/mrdox/Dom/Function.hpp b/include/mrdox/Dom/Function.hpp index 5cdf23301..7020f02d6 100644 --- a/include/mrdox/Dom/Function.hpp +++ b/include/mrdox/Dom/Function.hpp @@ -90,11 +90,21 @@ concept has_function_traits = requires { }; template -concept has_invoke_result_for_default_function_impl = - // Return type is void or convertible to dom::Value +concept has_invoke_result_convertible_to_dom_value = std::convertible_to::return_type, Value> || std::same_as::return_type, void>; +template +concept has_invoke_expected_result_convertible_to_dom_value = + detail::isExpected::return_type> && + (std::convertible_to::return_type::value_type, Value> || + std::same_as::return_type::value_type, void>); + +template +concept has_invoke_result_for_default_function_impl = + has_invoke_result_convertible_to_dom_value || + has_invoke_expected_result_convertible_to_dom_value; + template concept has_function_args_for_default_function_impl = // All arguments are convertible to dom::Value @@ -115,7 +125,8 @@ concept has_function_traits_for_default_function_impl = template concept function_traits_convertible_to_value = - has_function_traits && has_function_traits_for_default_function_impl; + has_function_traits && + has_function_traits_for_default_function_impl; //------------------------------------------------ // @@ -306,7 +317,7 @@ class DefaultFunctionImpl : public FunctionImpl char const* type_key() const noexcept override { - return "DefaultFunctionImpl"; + return "Function"; } Expected diff --git a/include/mrdox/Dom/Function.ipp b/include/mrdox/Dom/Function.ipp index 527e473e4..64c8cdf2f 100644 --- a/include/mrdox/Dom/Function.ipp +++ b/include/mrdox/Dom/Function.ipp @@ -33,7 +33,12 @@ Value Function:: operator()(Args&&... args) const { - return try_invoke(std::forward(args)...).value(); + auto exp = try_invoke(std::forward(args)...); + if (exp) + { + return *exp; + } + throw Exception(std::move(exp.error())); } template @@ -161,12 +166,12 @@ call_impl( using R = decltype( f_(arg_type > - >::get(args[I])...)); + >::get(args.get(I))...)); if (args.size() < sizeof...(I)) { Array clone; for (std::size_t i = 0; i < args.size(); ++i) - clone.emplace_back(args[i]); + clone.emplace_back(args.get(i)); std::size_t const diff = sizeof...(I) - args.size(); for (std::size_t i = 0; i < diff; ++i) clone.emplace_back(Value(Kind::Undefined)); @@ -174,14 +179,14 @@ call_impl( { f_(arg_type > - >::get(clone[I])...); + >::get(clone.get(I))...); return Value(Kind::Undefined); } else if constexpr (std::same_as>) { auto exp = f_(arg_type > - >::get(clone[I])...); + >::get(clone.get(I))...); if (!exp) { return Unexpected(exp.error()); @@ -192,21 +197,21 @@ call_impl( { return f_(arg_type > - >::get(clone[I])...); + >::get(clone.get(I))...); } } if constexpr (std::is_void_v) { f_(arg_type > - >::get(args[I])...); + >::get(args.get(I))...); return Value(Kind::Undefined); } else if constexpr (std::same_as>) { auto exp = f_(arg_type > - >::get(args[I])...); + >::get(args.get(I))...); if (!exp) { return Unexpected(exp.error()); @@ -217,7 +222,7 @@ call_impl( { return f_(arg_type > - >::get(args[I])...); + >::get(args.get(I))...); } } diff --git a/include/mrdox/Dom/Kind.hpp b/include/mrdox/Dom/Kind.hpp index d38eb5afe..ec0df56fc 100644 --- a/include/mrdox/Dom/Kind.hpp +++ b/include/mrdox/Dom/Kind.hpp @@ -19,6 +19,54 @@ namespace mrdox { namespace dom { /** The type of data in a Value. + + This is the type of data stored in a Value. + These types are loosely modeled after the + JavaScript types and data structures. + + Primitive values are Undefined, Null, Boolean, + Integer, and String. + + Undefined and Null are inhabited by a single + value each. The difference between Undefined + and Null is that Undefined is the default + value for a Value, while Null represents a + value that is explicitly set. Undefined is + used to represent things such as: + + @li An uninitialized Value + @li The Value returned from a function that failed to return a value + @li The result of accessing a nonexistent object property + @li The result of a `find` algorithm when no element is found + + This distinction is semantically important as + algorithms frequently need to distinguish between + these two cases. + + Booleans, Integers, and Strings are also primitive + values. This means they are deeply copied when assigned or + passed as a parameter. + + Other value types, such as Array, Object, and Function + are reference types, meaning that they are not copied + when assigned or passed as a parameter. Instead, the + reference is copied, and the original value is shared. + + These reference types are modeled after JavaScript + "Objects". All non-primitive types (Object types) + are derived from Object in Javascript. This means + types such as Array and Function represent a + relevant selection of built-in types that would + derive from Object in JavaScript. + + Objects are a collection of properties, which are + equivalent to key-value pairs. Property values + can be any type, including other Objects, allowing + for the creation of arbitrarily complex data + structures. + + @li https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures + */ enum class Kind { diff --git a/include/mrdox/Dom/Object.hpp b/include/mrdox/Dom/Object.hpp index 569e0e660..9e3b50b56 100644 --- a/include/mrdox/Dom/Object.hpp +++ b/include/mrdox/Dom/Object.hpp @@ -26,6 +26,46 @@ class String; class Value; /** A container of key and value pairs. + + Objects are a collection of properties, which are + equivalent to key-value pairs. Property values + can be any type, including other Objects, allowing + for the creation of arbitrarily complex data + structures. + + An Object is a non-primitive (or reference) type, + meaning that they are not copied when assigned or + passed as a parameter. Instead, the reference is + copied, and the original value is shared. + + These reference types are modeled after JavaScript + "Objects". All non-primitive types (Object types) + are derived from Object in Javascript. This means + types such as Array and Function represent a + relevant selection of built-in types that would + derive from Object in JavaScript. + + @par Properties + @parblock + Objects are a collection of properties, which are + equivalent to key-value pairs. There are two + kinds of properties: + + @li Data properties: Associates a key with a value. + @li Accessor properties: Associates a key with + one of two accessor functions (`get` and `set`), + which are used to retrieve or set the value. + + The internal representation of objects can determine + how properties are stored and the type of properties + being represented. + + Properties can also be enumerable or non-enumerable. + An enumerable property is one that is iterated by + the `visit` function. Non-enumerable properties can + only be accessed by name with the `get` and `set` + functions. + @endparblock */ class MRDOX_DECL Object final @@ -45,21 +85,21 @@ class MRDOX_DECL This is a read-only reference to an element. */ - struct reference; + using reference = value_type; /** A reference to an element. This is a read-only reference to an element. */ - struct const_reference; + using const_reference = reference; /** A pointer to an element. */ - using pointer = reference; + using pointer = value_type const*; /** A pointer to an element. */ - using const_pointer = reference; + using const_pointer = pointer; /** An unsigned integral type used for indexes and sizes. */ @@ -69,14 +109,6 @@ class MRDOX_DECL */ using difference_type = std::ptrdiff_t; - /** A constant iterator referencing an element in an Object. - */ - class iterator; - - /** A constant iterator referencing an element in an Object. - */ - using const_iterator = iterator; - /** The type of storage used by the default implementation. */ using storage_type = std::vector; @@ -115,25 +147,6 @@ class MRDOX_DECL */ Object(Object const& other) noexcept; - /** Assignment. - - Ownership of the object is transferred - to this, and ownership of the previous - contents is released. The moved-from - object behaves as if default constructed. - */ - Object& operator=(Object&&); - - /** Assignment. - - Shared ownership and copies of elements in - others are acquired by this. Ownership of - the previous contents is released. - */ - Object& operator=(Object const&) noexcept; - - //-------------------------------------------- - /** Constructor. This constructs an object from an existing @@ -159,6 +172,25 @@ class MRDOX_DECL */ explicit Object(storage_type list); + /** Assignment. + + Ownership of the object is transferred + to this, and ownership of the previous + contents is released. The moved-from + object behaves as if default constructed. + */ + Object& operator=(Object&&); + + /** Assignment. + + Shared ownership and copies of elements in + others are acquired by this. Ownership of + the previous contents is released. + */ + Object& operator=(Object const&) noexcept; + + //-------------------------------------------- + /** Return the implementation used by this object. */ auto @@ -180,41 +212,17 @@ class MRDOX_DECL */ std::size_t size() const; - /** Return the i-th element, without bounds checking. - - @param i The zero-based index of the element. - */ - reference get(std::size_t i) const; - - /** Return the i-th element, without bounds checking. - */ - reference operator[](size_type i) const; - - /** Return the element for a given key. + /** Return the element with the specified key */ - dom::Value operator[](std::string_view key) const; + Value get(std::string_view key) const; - /** Return the i-th element. - - @throw Exception `i >= size()` - */ - reference at(size_type i) const; + /// @copydoc get + Value at(std::string_view i) const; /** Return true if a key exists. */ bool exists(std::string_view key) const; - /** Return the value for a given key. - - If the key does not exist, a null value - is returned. - - @return The value, or null. - - @param key The key. - */ - Value find(std::string_view key) const; - /** Set or replace the value for a given key. This function inserts a new key or changes @@ -227,13 +235,50 @@ class MRDOX_DECL */ void set(String key, Value value) const; - /** Return an iterator to the beginning of the range of elements. - */ - iterator begin() const; + /** Invoke the visitor for each key/value pair - /** Return an iterator to the end of the range of elements. - */ - iterator end() const; + The visitor function must return `true` to + continue iteration, or `false` to stop + iteration early. + + @return `true` if the visitor returned `true` + for all elements, otherwise `false`. + + */ + template + requires + std::invocable && + std::same_as, bool> + bool visit(F&& fn) const; + + /** Invoke the visitor for each key/value pair + + The visitor function must return `void` to + continue iteration, or an `Unexpected` to + stop iteration early. + + If an error is returned, the iteration stops + and the error is returned from this function. + + @return `void` if the visitor returned did not + return an error for any element, otherwise + `E`. + + */ + template + requires + std::invocable && + detail::isExpected> + Expected::error_type> + visit(F&& fn) const; + + /** Invoke the visitor for each key/value pair + */ + template + requires + std::invocable && + std::same_as, void> + void visit(F&& fn) const; /** Swap two objects. */ @@ -295,9 +340,6 @@ class MRDOX_DECL /// @copydoc Object::reference using reference = Object::reference; - /// @copydoc Object::iterator - using iterator = Object::iterator; - /** Destructor. */ virtual ~ObjectImpl(); @@ -306,21 +348,25 @@ class MRDOX_DECL */ virtual char const* type_key() const noexcept; - /** Return the number of key/value pairs in the object. - */ - virtual std::size_t size() const = 0; - - /** Return the i-th key/value pair, without bounds checking. - */ - virtual reference get(std::size_t i) const = 0; - /** Return the value for the specified key, or null. */ - virtual Value find(std::string_view key) const = 0; + virtual Value get(std::string_view key) const = 0; /** Insert or set the given key/value pair. */ virtual void set(String key, Value value) = 0; + + /** Invoke the visitor for each key/value pair. + */ + virtual bool visit(std::function) const = 0; + + /** Return the number of properties in the object. + */ + virtual std::size_t size() const = 0; + + /** Determine if a key exists. + */ + virtual bool exists(std::string_view key) const; }; /** Return a new object using a custom implementation. @@ -351,9 +397,10 @@ class MRDOX_DECL explicit DefaultObjectImpl( storage_type entries) noexcept; std::size_t size() const override; - reference get(std::size_t) const override; - Value find(std::string_view) const override; + Value get(std::string_view) const override; void set(String, Value) override; + bool visit(std::function) const override; + bool exists(std::string_view key) const override; private: storage_type entries_; @@ -366,6 +413,13 @@ class MRDOX_DECL //------------------------------------------------ /** A lazy Object implementation. + + This implementation is used to construct an + Object on demand. + + The underlying object storage is only + initialized when the first property is + set or accessed. */ class MRDOX_DECL LazyObjectImpl : public ObjectImpl @@ -386,9 +440,10 @@ class MRDOX_DECL public: std::size_t size() const override; - reference get(std::size_t i) const override; - Value find(std::string_view key) const override; + Value get(std::string_view key) const override; void set(String key, Value value) override; + bool visit(std::function) const override; + bool exists(std::string_view key) const override; }; } // dom diff --git a/include/mrdox/Dom/Object.ipp b/include/mrdox/Dom/Object.ipp index 424c5e13f..94c7bee83 100644 --- a/include/mrdox/Dom/Object.ipp +++ b/include/mrdox/Dom/Object.ipp @@ -64,180 +64,6 @@ struct Object::value_type } }; -//------------------------------------------------ -// -// Object::reference -// -//------------------------------------------------ - -struct MRDOX_DECL - Object::reference -{ - String const& key; - Value const& value; - - reference(reference const&) = default; - - reference( - String const& key_, - Value const& value_) noexcept - : key(key_) - , value(value_) - { - } - - reference( - value_type const& kv) noexcept - : key(kv.key) - , value(kv.value) - { - } - - operator value_type() const - { - return value_type(key, value); - } - - reference const* operator->() noexcept - { - return this; - } - - reference const* operator->() const noexcept - { - return this; - } -}; - -//------------------------------------------------ -// -// Object::iterator -// -//------------------------------------------------ - -class MRDOX_DECL - Object::iterator -{ - ObjectImpl const* obj_ = nullptr; - std::size_t i_ = 0; - - friend class Object; - - iterator( - ObjectImpl const& obj, - std::size_t i) noexcept - : obj_(&obj) - , i_(i) - { - } - -public: - using value_type = Object::value_type; - using reference = Object::reference; - using difference_type = std::ptrdiff_t; - using size_type = std::size_t; - using pointer = reference; - using iterator_category = - std::random_access_iterator_tag; - - iterator() = default; - - reference operator*() const noexcept - { - return obj_->get(i_); - } - - pointer operator->() const noexcept - { - return obj_->get(i_); - } - - reference operator[](difference_type n) const noexcept - { - return obj_->get(i_ + n); - } - - iterator& operator--() noexcept - { - --i_; - return *this; - } - - iterator operator--(int) noexcept - { - auto temp = *this; - --*this; - return temp; - } - - iterator& operator++() noexcept - { - ++i_; - return *this; - } - - iterator operator++(int) noexcept - { - auto temp = *this; - ++*this; - return temp; - } - - auto operator<=>(iterator const& other) const noexcept - { - MRDOX_ASSERT(obj_ == other.obj_); - return i_ <=> other.i_; - } - -#if 1 - // VFALCO Why does ranges need these? Isn't <=> enough? - bool operator==(iterator const& other) const noexcept - { - MRDOX_ASSERT(obj_ == other.obj_); - return i_ == other.i_; - } - - bool operator!=(iterator const& other) const noexcept - { - MRDOX_ASSERT(obj_ == other.obj_); - return i_ != other.i_; - } -#endif - - iterator& operator-=(difference_type n) noexcept - { - i_ -= n; - return *this; - } - - iterator& operator+=(difference_type n) noexcept - { - i_ += n; - return *this; - } - - iterator operator-(difference_type n) const noexcept - { - return iterator(*obj_, i_ - n); - } - - iterator operator+(difference_type n) const noexcept - { - return iterator(*obj_, i_ + n); - } - - difference_type operator-(iterator other) const noexcept - { - MRDOX_ASSERT(obj_ == other.obj_); - return static_cast(i_) - other.i_; - } - - friend iterator operator+(difference_type n, iterator it) noexcept - { - return it + n; - } -}; - //------------------------------------------------ // // Object @@ -259,31 +85,14 @@ inline std::size_t Object::size() const return impl_->size(); } -inline auto Object::get(std::size_t i) const -> reference -{ - return impl_->get(i); -} - -inline auto Object::operator[](size_type i) const -> reference -{ - return get(i); -} - -inline auto Object::operator[](std::string_view key) const -> dom::Value +inline auto Object::get(std::string_view key) const -> Value { - return this->find(key); + return impl_->get(key); } -inline auto Object::at(size_type i) const -> reference +inline auto Object::at(std::string_view key) const -> Value { - if(i < size()) - return get(i); - Error("out of range").Throw(); -} - -inline Value Object::find(std::string_view key) const -{ - return impl_->find(key); + return this->get(key); } inline void Object::set(String key, Value value) const @@ -291,14 +100,46 @@ inline void Object::set(String key, Value value) const impl_->set(std::move(key), std::move(value)); } -inline auto Object::begin() const -> iterator +template +requires + std::invocable && + std::same_as, bool> +bool Object::visit(F&& fn) const { - return iterator(*impl_, 0); + return impl_->visit(std::forward(fn)); } -inline auto Object::end() const -> iterator +template +requires + std::invocable && + std::same_as, void> +void Object::visit(F&& fn) const { - return iterator(*impl_, impl_->size()); + impl_->visit([f=std::forward(fn)](String k, Value v) { + f(std::move(k), std::move(v)); + return true; + }); +} + +template +requires + std::invocable && + detail::isExpected> +Expected::error_type> +Object::visit(F&& fn) const +{ + using E = typename std::invoke_result_t::error_type; + Expected res; + impl_->visit([f=std::forward(fn), &res](String k, Value v) { + Expected exp = f(std::move(k), std::move(v)); + if (!exp.has_value()) + { + res = Unexpected(exp.error()); + return false; + } + return true; + }); + return res; } } // dom @@ -307,28 +148,6 @@ inline auto Object::end() const -> iterator //------------------------------------------------ -template< - template class TQual, - template class UQual> -struct std::basic_common_reference< - ::clang::mrdox::dom::Object::value_type, - ::clang::mrdox::dom::Object::reference, - TQual, UQual> -{ - using type = ::clang::mrdox::dom::Object::reference; -}; - -template< - template class TQual, - template class UQual> -struct std::basic_common_reference< - ::clang::mrdox::dom::Object::reference, - ::clang::mrdox::dom::Object::value_type, - TQual, UQual> -{ - using type = ::clang::mrdox::dom::Object::reference; -}; - template<> struct fmt::formatter : fmt::formatter diff --git a/include/mrdox/Dom/String.hpp b/include/mrdox/Dom/String.hpp index 4f8c618ea..25648ef0e 100644 --- a/include/mrdox/Dom/String.hpp +++ b/include/mrdox/Dom/String.hpp @@ -270,7 +270,7 @@ class MRDOX_DECL } /// @overload - template S> + template friend auto operator+( S const& lhs, String const& rhs) noexcept { @@ -280,7 +280,7 @@ class MRDOX_DECL } /// @overload - template S> + template friend auto operator+( String const& lhs, S const& rhs) noexcept { diff --git a/include/mrdox/Dom/Value.hpp b/include/mrdox/Dom/Value.hpp index 7984d128e..8d6f31a39 100644 --- a/include/mrdox/Dom/Value.hpp +++ b/include/mrdox/Dom/Value.hpp @@ -139,14 +139,14 @@ class MRDOX_DECL template requires std::constructible_from Value(std::optional const& opt) - : Value(opt.value_or(Value())) + : Value(opt.value_or(Value(Kind::Undefined))) { } template requires std::constructible_from Value(Optional const& opt) - : Value(opt ? Value(*opt) : Value()) + : Value(opt ? Value(*opt) : Value(Kind::Undefined)) { } @@ -265,6 +265,11 @@ class MRDOX_DECL Array const& getArray() const; + /** @copydoc getArray() + */ + Array& + getArray(); + /** Return the object. @throw Exception `! isObject()` @@ -286,24 +291,42 @@ class MRDOX_DECL is returned. */ dom::Value - operator[](std::string_view key) const; + get(std::string_view key) const; template S> dom::Value - operator[](S const& key) const + get(S const& key) const { - return operator[](std::string_view(key)); + return get(std::string_view(key)); } /** Return the element at a given index. */ dom::Value - operator[](std::size_t i) const; + get(std::size_t i) const; /** Return the element at a given index or key. */ dom::Value - operator[](dom::Value const& i) const; + get(dom::Value const& i) const; + + /** Lookup a sequence of keys. + + This function is equivalent to calling `get` + multiple times, once for each key in the sequence + of dot-separated keys. + + @return The value at the end of the sequence, or + a Value of type @ref Kind::Undefined if any key + is not found. + */ + dom::Value + lookup(std::string_view keys) const; + + /** Set or replace the value for a given key. + */ + void + set(String const& key, Value const& value); /** Return true if a key exists. */ @@ -351,32 +374,6 @@ class MRDOX_DECL return toString(*this); } - /** Set or replace the value for a given key. - */ - void - set(String const& key, Value const& value) - { - if (isObject()) - { - obj_.set(key, value); - return; - } - if (isArray()) - { - std::string_view idxStr = key; - std::size_t idx = 0; - auto res = std::from_chars( - idxStr.data(), - idxStr.data() + idxStr.size(), - idx); - if (res.ec == std::errc()) - { - arr_.set(idx, value); - } - return; - } - } - /** Swap two values. */ void diff --git a/include/mrdox/Support/Handlebars.hpp b/include/mrdox/Support/Handlebars.hpp index 72d0ec78e..0cb3de89e 100644 --- a/include/mrdox/Support/Handlebars.hpp +++ b/include/mrdox/Support/Handlebars.hpp @@ -991,7 +991,7 @@ escapeExpression( } if (v.isObject() && v.getObject().exists("toHTML")) { - dom::Value fn = v.getObject().find("toHTML"); + dom::Value fn = v.getObject().get("toHTML"); if (fn.isFunction()) { return toString(fn.getFunction()()); } diff --git a/src/lib/Dom/Array.cpp b/src/lib/Dom/Array.cpp index f858b71d5..9686fb5f0 100644 --- a/src/lib/Dom/Array.cpp +++ b/src/lib/Dom/Array.cpp @@ -64,6 +64,10 @@ operator=( bool operator==(dom::Array const& a, dom::Array const& b) noexcept { + if (a.impl_ == b.impl_) + { + return true; + } Array::size_type const an = a.size(); Array::size_type const bn = b.size(); if (an != bn) @@ -72,7 +76,7 @@ operator==(dom::Array const& a, dom::Array const& b) noexcept } for (std::size_t i = 0; i < an; ++i) { - if (a[i] != b[i]) + if (a.get(i) != b.get(i)) { return false; } @@ -96,16 +100,16 @@ operator<=>(Array const& a, Array const& b) noexcept Array::size_type const n = an; for (std::size_t i = 0; i < n; ++i) { - if (a[i] < b[i]) + if (a.get(i) < b.get(i)) { return std::strong_ordering::less; } - if (b[i] < a[i]) + if (b.get(i) < a.get(i)) { return std::strong_ordering::greater; } } - return std::strong_ordering::greater; + return std::strong_ordering::equal; } std::string @@ -140,7 +144,7 @@ char const* ArrayImpl:: type_key() const noexcept { - return "array"; + return "Array"; } void @@ -192,7 +196,10 @@ void DefaultArrayImpl:: set(size_type i, Value v) { - MRDOX_ASSERT(i < elements_.size()); + if (i >= elements_.size()) + { + elements_.resize(i + 1, Kind::Undefined); + } elements_[i] = std::move(v); } @@ -204,6 +211,13 @@ emplace_back( elements_.emplace_back(std::move(value)); } +char const* +DefaultArrayImpl:: +type_key() const noexcept +{ + return "Array"; +} + } // dom } // mrdox } // clang diff --git a/src/lib/Dom/Function.cpp b/src/lib/Dom/Function.cpp index 15d139a1e..f3162640d 100644 --- a/src/lib/Dom/Function.cpp +++ b/src/lib/Dom/Function.cpp @@ -84,7 +84,7 @@ char const* FunctionImpl:: type_key() const noexcept { - return "function"; + return "Function"; } } // dom diff --git a/src/lib/Dom/Object.cpp b/src/lib/Dom/Object.cpp index 2ce17b138..608fdf02f 100644 --- a/src/lib/Dom/Object.cpp +++ b/src/lib/Dom/Object.cpp @@ -18,9 +18,6 @@ namespace clang { namespace mrdox { namespace dom { -static_assert(std::random_access_iterator); -static_assert(std::ranges::random_access_range); - //------------------------------------------------ // // Object @@ -81,11 +78,7 @@ bool Object:: exists(std::string_view key) const { - for(auto const& kv : *this) - if(kv.key == key) - return true; - - return false; + return impl_->exists(key); } bool @@ -95,9 +88,10 @@ operator==(Object const& a, Object const& b) noexcept } std::string -toString(Object const&) +toString(Object const& obj) { - return "[object Object]"; + return fmt::format( + "[object {}]", obj.type_key()); } //------------------------------------------------ @@ -113,7 +107,20 @@ char const* ObjectImpl:: type_key() const noexcept { - return "object"; + return "Object"; +} + +bool +ObjectImpl::exists(std::string_view key) const +{ + return !visit([&](String const& k, Value const&) + { + if (k == key) + { + return false; + } + return true; + }); } //------------------------------------------------ @@ -141,25 +148,19 @@ size() const auto DefaultObjectImpl:: -get(std::size_t i) const -> - reference -{ - MRDOX_ASSERT(i < entries_.size()); - return entries_[i]; -} - -Value -DefaultObjectImpl:: -find(std::string_view key) const +get(std::string_view key) const -> + Value { auto it = std::ranges::find_if( entries_.begin(), entries_.end(), [key](auto const& kv) - { - return kv.key == key; - }); - if(it == entries_.end()) - return nullptr; + { + return kv.key == key; + }); + if (it == entries_.end()) + { + return Kind::Undefined; + } return it->value; } @@ -167,6 +168,7 @@ void DefaultObjectImpl:: set(String key, Value value) { + auto it = std::ranges::find_if( entries_.begin(), entries_.end(), [key](auto const& kv) @@ -180,6 +182,31 @@ set(String key, Value value) it->value = std::move(value); } +bool +DefaultObjectImpl:: +visit(std::function visitor) const +{ + for (auto const& kv : entries_) + { + if (!visitor(kv.key, kv.value)) + { + return false; + } + } + return true; +} + +bool +DefaultObjectImpl::exists(std::string_view key) const { + auto it = std::ranges::find_if( + entries_.begin(), entries_.end(), + [key](auto const& kv) + { + return kv.key == key; + }); + return it != entries_.end(); +} + //------------------------------------------------ // // LazyObjectImpl @@ -209,24 +236,31 @@ size() const auto LazyObjectImpl:: -get(std::size_t i) const -> - reference +get(std::string_view key) const -> + Value +{ + return obj().get(key); +} + +void +LazyObjectImpl:: +set(String key, Value value) { - return obj().get(i); + return obj().set(std::move(key), std::move(value)); } -Value +bool LazyObjectImpl:: -find(std::string_view key) const +visit(std::function visitor) const { - return obj().find(key); + return obj().visit(std::move(visitor)); } -void +bool LazyObjectImpl:: -set(String key, Value value) +exists(std::string_view key) const { - return obj().set(std::move(key), std::move(value)); + return obj().exists(key); } } // dom diff --git a/src/lib/Dom/Value.cpp b/src/lib/Dom/Value.cpp index 6ea177af5..7a07eb64d 100644 --- a/src/lib/Dom/Value.cpp +++ b/src/lib/Dom/Value.cpp @@ -242,10 +242,12 @@ type_key() const noexcept switch(kind_) { using enum Kind; + case Undefined: return "undefined"; case Null: return "null"; - case Boolean: return "bool"; + case Boolean: return "boolean"; case Integer: return "integer"; case String: return "string"; + case SafeString: return "safeString"; case Array: return arr_.type_key(); case Object: return obj_.type_key(); case Function: return fn_.type_key(); @@ -298,6 +300,17 @@ getArray() const Error("not an Array").Throw(); } +Array& +Value:: +getArray() +{ + if(kind_ == Kind::Array) + { + return arr_; + } + Error("not an Array").Throw(); +} + Object const& Value:: getObject() const @@ -322,13 +335,13 @@ getFunction() const dom::Value Value:: -operator[](std::string_view key) const +get(std::string_view key) const { if (kind_ == Kind::Object) { - return obj_[key]; + return obj_.get(key); } - if (kind_ == Kind::Array) + if (kind_ == Kind::Array || kind_ == Kind::String) { auto isDigit = [](auto c) { return c >= '0' && c <= '9'; @@ -340,7 +353,15 @@ operator[](std::string_view key) const key.data(), key.data() + key.size(), idx); if (res.ec == std::errc()) { - return arr_[idx]; + if (kind_ == Kind::String && idx < str_.size()) + { + return String(std::string_view( + str_.get().data() + idx, 1)); + } + else if (kind_ == Kind::Array && idx < arr_.size()) + { + return arr_.get(idx); + } } } } @@ -349,34 +370,88 @@ operator[](std::string_view key) const dom::Value Value:: -operator[](std::size_t i) const +get(std::size_t i) const { if (kind_ == Kind::Array) { - return arr_[i]; + return arr_.get(i); + } + if (kind_ == Kind::String) + { + if (i < str_.size()) + { + return str_.get()[i]; + } } if (kind_ == Kind::Object) { - return obj_[i].value; + std::string key = std::to_string(i); + return obj_.get(key); } return {}; } dom::Value Value:: -operator[](dom::Value const& i) const +get(dom::Value const& i) const { if (i.isInteger()) { - return operator[](static_cast(i.getInteger())); + return get(static_cast(i.getInteger())); } if (i.isString() || i.isSafeString()) { - return operator[](i.getString().get()); + return get(i.getString().get()); } return {}; } +dom::Value +Value:: +lookup(std::string_view keys) const +{ + dom::Value cur = *this; + std::size_t pos = keys.find('.'); + std::string_view key = keys.substr(0, pos); + while (pos != std::string_view::npos) + { + cur = cur.get(key); + if (cur.isUndefined()) + { + return cur; + } + keys = keys.substr(pos + 1); + pos = keys.find('.'); + key = keys.substr(0, pos); + } + return cur.get(key); +} + +void +Value:: +set(String const& key, Value const& value) +{ + if (isObject()) + { + obj_.set(key, value); + return; + } + if (isArray()) + { + std::string_view idxStr = key; + std::size_t idx = 0; + auto res = std::from_chars( + idxStr.data(), + idxStr.data() + idxStr.size(), + idx); + if (res.ec == std::errc()) + { + arr_.set(idx, value); + } + return; + } +} + bool Value:: exists(std::string_view key) const @@ -597,7 +672,9 @@ stringify( { switch(value.kind()) { + case Kind::Undefined: case Kind::Null: + case Kind::Function: dest.append("null"); break; case Kind::Boolean: @@ -614,6 +691,7 @@ stringify( dest.append(std::to_string(value.getInteger())); break; case Kind::String: + case Kind::SafeString: { std::string_view const s = value.getString().get(); @@ -642,8 +720,13 @@ stringify( Array::size_type n = arr.size(); for(std::size_t i = 0; i < n; ++i) { + dom::Value value = arr.get(i); + if (value.isUndefined() || value.isFunction()) + { + continue; + } dest.append(indent); - stringify(dest, arr.get(i), indent, visited); + stringify(dest, value, indent, visited); if(i != n - 1) { dest.push_back(','); @@ -670,30 +753,31 @@ stringify( } indent.append(" "); dest.append("{\n"); - auto it = obj.begin(); - while (it != obj.end()) + bool is_first = true; + obj.visit([&](String const& key, Value const& value) { - auto it0 = it++; - dest.append(indent); - dest.push_back('"'); - JSON::escape(dest, it0->key); - dest.append("\" : "); - stringify(dest, - it0->value, indent, visited); - if(it != obj.end()) + if (value.isUndefined() || value.isFunction()) + { + return; + } + if (!is_first) { dest.push_back(','); + dest.push_back('\n'); } - dest.push_back('\n'); - } + is_first = false; + dest.append(indent); + dest.push_back('"'); + JSON::escape(dest, key); + dest.append("\": "); + stringify(dest, value, indent, visited); + }); + dest.push_back('\n'); indent.resize(indent.size() - 4); dest.append(indent); dest.append("}"); break; } - case Kind::Function: - dest.append("\"(function)\""); - break; default: MRDOX_UNREACHABLE(); } @@ -774,6 +858,10 @@ operator<=>(dom::Value const& lhs, dom::Value const& rhs) noexcept case Object: return lhs.obj_ <=> rhs.obj_; case Function: + if (lhs.fn_.impl() == rhs.fn_.impl()) + { + return std::strong_ordering::equal; + } return std::strong_ordering::equivalent; default: MRDOX_UNREACHABLE(); @@ -803,7 +891,7 @@ toString( case Kind::SafeString: return std::string(value.str_.get()); case Kind::Function: - return "[function]"; + return "[object Function]"; default: MRDOX_UNREACHABLE(); } diff --git a/src/lib/Support/Handlebars.cpp b/src/lib/Support/Handlebars.cpp index 3596b248c..bf75b4e12 100644 --- a/src/lib/Support/Handlebars.cpp +++ b/src/lib/Support/Handlebars.cpp @@ -104,56 +104,63 @@ class OverlayObjectImpl : public dom::ObjectImpl , child_(std::move(child)) {} - std::size_t size() const override { + std::size_t size() const override + { std::size_t n = parent_.size() + child_.size(); - for (auto const& [key, value] : child_) + child_.visit([&](dom::String const& key, dom::Value const& value) { if (parent_.exists(key)) { --n; } - } + }); return n; }; - reference get(std::size_t i) const override { - if (i < child_.size()) + dom::Value get(std::string_view key) const override + { + if (child_.exists(key)) { - return child_.get(i); + return child_.get(key); } - MRDOX_ASSERT(i < size()); - std::size_t pi = i - child_.size(); - size_t const n = parent_.size(); - for (std::size_t j = 0; j < n; ++j) + if (parent_.exists(key)) { - auto el = parent_.get(j); - if (child_.exists(el.key)) - { - ++pi; - } - else if (j == pi) - { - return el; - } + return parent_.get(key); } - MRDOX_UNREACHABLE(); + return dom::Kind::Undefined; + } + + void set(dom::String key, dom::Value value) override + { + child_.set(key, std::move(value)); }; - dom::Value find(std::string_view key) const override { - if (child_.exists(key)) + bool visit(std::function fn) const override + { + if (!child_.visit(fn)) { - return child_.find(key); + return false; } - if (parent_.exists(key)) + auto visit_if_not_inchild = [&]( + dom::String const& key, dom::Value const& value) { - return parent_.find(key); + if (!child_.exists(key)) + { + return fn(key, value); + } + return true; + }; + if (!parent_.visit(visit_if_not_inchild)) + { + return false; } - return nullptr; + return true; } - void set(dom::String key, dom::Value value) override { - child_.set(key, std::move(value)); - }; + bool exists(std::string_view key) const override + { + return child_.exists(key) || parent_.exists(key); + } }; dom::Object @@ -733,7 +740,7 @@ lookupPropertyImpl( } else { - cur = context.find(literalSegment); + cur = context.get(literalSegment); } // Recursively get more values from current value @@ -750,7 +757,7 @@ lookupPropertyImpl( auto obj = cur.getObject(); if (obj.exists(literalSegment)) { - cur = obj.find(literalSegment); + cur = obj.get(literalSegment); } else { @@ -899,7 +906,7 @@ struct defaultLogger { void operator()(dom::Array const& args) const { - dom::Value level = lookupLevel(args[0]); + dom::Value level = lookupLevel(args.at(0)); if (!level.isInteger() || level.getInteger() > level_) { return; } @@ -1629,149 +1636,86 @@ struct HbsHelperObjectImpl ~HbsHelperObjectImpl() override = default; char const* - type_key() const noexcept override { - return "HandlebarsHelperObject"; + type_key() const noexcept override + { + return "handlebarsHelperObject"; } - std::size_t size() const override { + std::size_t size() const override + { return 13 + overlay_.size(); } - reference get(std::size_t i) const override { - switch (i) { - case 0: return { "name", name_ }; - case 1: return { "context", context_ }; - case 2: return { "data", data_ }; - case 3: return { "log", log_ }; - case 4: return { "hash", hash_ }; - case 5: return { "ids", ids_ }; - case 6: return { "hashIds", hashIds_ }; - case 7: return { "lookupProperty", lookupProperty_ }; - case 8: return { "blockParams", blockParams_ }; - case 9: return { "write", write_ }; - case 10: return { "fn", fn_ }; - case 11: return { "inverse", inverse_ }; - case 12: return { "write_inverse", write_inverse_ }; - default: return overlay_.get(i - 13); - } - } - - dom::Value find(std::string_view key) const override { - if (key == "name") - { - return name_; - } - else if (key == "context") - { - return context_; - } - else if (key == "data") - { - return data_; - } - else if (key == "log") - { - return log_; - } - else if (key == "hash") - { - return hash_; - } - else if (key == "ids") - { - return ids_; - } - else if (key == "hashIds") - { - return hashIds_; - } - else if (key == "lookupProperty") - { - return lookupProperty_; - } - else if (key == "blockParams") - { - return blockParams_; - } - else if (key == "write") - { - return write_; - } - else if (key == "fn") - { - return fn_; - } - else if (key == "inverse") - { - return inverse_; - } - else if (key == "write_inverse") - { - return write_inverse_; - } - else - { - return overlay_.find(key); - } - } - - void set(dom::String key, dom::Value value) override { - if (key == "name") - { - name_ = value; - } - else if (key == "context") - { - context_ = value; - } - else if (key == "data") - { - data_ = value; - } - else if (key == "log") - { - log_ = value; - } - else if (key == "hash") - { - hash_ = value; - } - else if (key == "ids") - { - ids_ = value; - } - else if (key == "hashIds") - { - hashIds_ = value; - } - else if (key == "lookupProperty") - { - lookupProperty_ = value; - } - else if (key == "blockParams") - { - blockParams_ = value; - } - else if (key == "write") - { - write_ = value; - } - else if (key == "fn") - { - fn_ = value; - } - else if (key == "inverse") - { - inverse_ = value; - } - else if (key == "write_inverse") - { - write_inverse_ = value; - } - else - { - overlay_.set(key, value); - } + dom::Value get(std::string_view key) const override + { + if (key == "name") return name_; + if (key == "context") return context_; + if (key == "data") return data_; + if (key == "log") return log_; + if (key == "hash") return hash_; + if (key == "ids") return ids_; + if (key == "hashIds") return hashIds_; + if (key == "lookupProperty") return lookupProperty_; + if (key == "blockParams") return blockParams_; + if (key == "write") return write_; + if (key == "fn") return fn_; + if (key == "inverse") return inverse_; + if (key == "write_inverse") return write_inverse_; + return overlay_.get(key); + } + + void set(dom::String key, dom::Value value) override + { + if (key == "name") { name_ = value; return; } + if (key == "context") { context_ = value; return; } + if (key == "data") { data_ = value; return; } + if (key == "log") { log_ = value; return; } + if (key == "hash") { hash_ = value; return; } + if (key == "ids") { ids_ = value; return; } + if (key == "hashIds") { hashIds_ = value; return; } + if (key == "lookupProperty") { lookupProperty_ = value; return; } + if (key == "blockParams") { blockParams_ = value; return; } + if (key == "write") { write_ = value; return; } + if (key == "fn") { fn_ = value; return; } + if (key == "inverse") { inverse_ = value; return; } + if (key == "write_inverse") { write_inverse_ = value; return; } + overlay_.set(key, value); + } + + bool visit(std::function visitor) const override + { + if (!visitor("name", name_)) return false; + if (!visitor("context", context_)) return false; + if (!visitor("data", data_)) return false; + if (!visitor("log", log_)) return false; + if (!visitor("hash", hash_)) return false; + if (!visitor("ids", ids_)) return false; + if (!visitor("hashIds", hashIds_)) return false; + if (!visitor("lookupProperty", lookupProperty_)) return false; + if (!visitor("blockParams", blockParams_)) return false; + if (!visitor("write", write_)) return false; + if (!visitor("fn", fn_)) return false; + if (!visitor("inverse", inverse_)) return false; + if (!visitor("write_inverse", write_inverse_)) return false; + return overlay_.visit(visitor); + } + + bool exists(std::string_view key) const override + { + if (key == "name") return true; + if (key == "context") return true; + if (key == "data") return true; + if (key == "log") return true; + if (key == "hash") return true; + if (key == "ids") return true; + if (key == "hashIds") return true; + if (key == "lookupProperty") return true; + if (key == "blockParams") return true; + if (key == "write") return true; + if (key == "fn") return true; + if (key == "inverse") return true; + if (key == "write_inverse") return true; + return overlay_.exists(key); } }; @@ -1870,7 +1814,7 @@ evalExpr( popFirstSegment(expression); if (state.data.exists("root")) { - data = state.data.find("root"); + data = state.data.get("root"); } else { @@ -1975,13 +1919,13 @@ evalExpr( } // ============================================================== - // Whole context object key + // Literal object key // ============================================================== if (context.kind() == dom::Kind::Object) { auto& obj = context.getObject(); if (obj.exists(expression)) { - return Res{obj.find(expression), true, false}; + return Res{obj.get(expression), true, false}; } } @@ -2360,7 +2304,7 @@ renderExpression( // ============================================================== if (state.blockValues.exists(tag.helper)) { - auto v = state.blockValues.find(tag.helper); + auto v = state.blockValues.get(tag.helper); format_to(out, v, opt2); if (tag.removeRWhitespace) { state.templateText = trim_lspaces(state.templateText); @@ -2451,7 +2395,7 @@ renderExpression( { Error e = exp2.error(); auto res = find_position_in_text(state.templateText0, helper_expr); - std::string msg(e.reason()); + std::string const& msg = e.reason(); if (res) { return Unexpected(HandlebarsError(msg, res.line, res.column, res.pos)); @@ -2517,7 +2461,7 @@ setupArgs( cb.set("ids", {}); cb.set("hashIds", {}); } - dom::Object hash = cb.find("hash").getObject(); + dom::Object hash = cb.get("hash").getObject(); while (findExpr(expr, expression)) { // ========================================== @@ -2547,7 +2491,7 @@ setupArgs( MRDOX_TRY(auto res, evalExpr(context, expr, state, opt, true)); args.emplace_back(res.value); if (opt.trackIds) { - dom::Array ids = cb.find("ids").getArray(); + dom::Array ids = cb.get("ids").getArray(); if (res.isLiteral) { ids.emplace_back(nullptr); @@ -2558,23 +2502,23 @@ setupArgs( } else if (res.fromBlockParams) { - std::size_t n = state.blockValuePaths.size(); dom::Value IdVal = expr; - for (std::size_t i = 0; i < n; ++i) + state.blockValuePaths.visit( + [&](dom::String const& key, dom::Value const& value) { - auto blockValuePath = state.blockValuePaths[i]; - if (expr.starts_with(blockValuePath.key)) + if (expr.starts_with(key)) { - if (blockValuePath.value.isString()) + if (value.isString()) { std::string idStr; - idStr += blockValuePath.value.getString(); - idStr += expr.substr(blockValuePath.key.size()); + idStr += value.getString(); + idStr += expr.substr(key.size()); IdVal = idStr; } - break; + return false; } - } + return true; + }); ids.emplace_back(IdVal); } else @@ -2591,7 +2535,7 @@ setupArgs( MRDOX_TRY(auto res, evalExpr(context, v, state, opt, true)); hash.set(k, res.value); if (opt.trackIds) { - dom::Object hashIds = cb.find("hashIds").getObject(); + dom::Object hashIds = cb.get("hashIds").getObject(); if (res.isLiteral) { hashIds.set(k, nullptr); } @@ -2605,7 +2549,7 @@ setupArgs( } } cb.set("lookupProperty", dom::makeInvocable([&state, &opt]( - dom::Value const& obj, dom::Value const& field) + dom::Value const& obj, dom::Value const& field) -> dom::Value { return lookupPropertyImpl(obj, field, state, opt).value().first; })); @@ -2771,7 +2715,7 @@ renderPartial( // Populate with arguments // ========================================== bool partialCtxChanged = false; - dom::Value prevContextPath = state.data.find("contextPath"); + dom::Value prevContextPath = state.data.get("contextPath"); if (!tag.arguments.empty()) { // create context from specified keys @@ -2814,7 +2758,7 @@ renderPartial( if (opt.trackIds) { std::string contextPath = appendContextPath( - state.data.find("contextPath"), expr); + state.data.get("contextPath"), expr); state.data.set("contextPath", contextPath); } if (res.found) @@ -3078,7 +3022,7 @@ renderBlock( // Data if (optObj.exists("data")) { - dom::Value dataV = optObj.find("data"); + dom::Value dataV = optObj.get("data"); if (dataV.isObject()) { state.data = dataV.getObject(); @@ -3088,13 +3032,13 @@ renderBlock( // Block params if (optObj.exists("blockParams")) { - dom::Value blockParamsV = optObj.find("blockParams"); + dom::Value blockParamsV = optObj.get("blockParams"); if (blockParamsV.isArray()) { dom::Object newBlockValues; dom::Array const& blockParams = blockParamsV.getArray(); for (std::size_t i = 0; i < blockParamIds.size(); ++i) { - newBlockValues.set(blockParamIds[i], blockParams[i]); + newBlockValues.set(blockParamIds[i], blockParams.get(i)); } dom::Object blockValuesOverlay = createFrame(newBlockValues, state.blockValues); @@ -3105,14 +3049,14 @@ renderBlock( // Block param paths if (optObj.exists("blockParamPaths")) { - dom::Value blockParamPathsV = optObj.find("blockParamPaths"); + dom::Value blockParamPathsV = optObj.get("blockParamPaths"); if (blockParamPathsV.isArray()) { dom::Array const& blockParamPaths = blockParamPathsV.getArray(); dom::Object newBlockValuePaths; for (std::size_t i = 0; i < blockParamIds.size(); ++i) { - newBlockValuePaths.set(blockParamIds[i], blockParamPaths[i]); + newBlockValuePaths.set(blockParamIds[i], blockParamPaths.get(i)); } dom::Object blockValuePathsOverlay = createFrame(newBlockValuePaths, state.blockValuePaths); @@ -3173,7 +3117,7 @@ renderBlock( // Data if (optObj.exists("data")) { - dom::Value dataV = optObj.find("data"); + dom::Value dataV = optObj.get("data"); if (dataV.isObject()) { state.data = dataV.getObject(); @@ -3183,13 +3127,13 @@ renderBlock( // Block params if (optObj.exists("blockParams")) { - dom::Value blockParamsV = optObj.find("blockParams"); + dom::Value blockParamsV = optObj.get("blockParams"); if (blockParamsV.isArray()) { dom::Object newBlockValues; dom::Array const& blockParams = blockParamsV.getArray(); for (std::size_t i = 0; i < blockParamIds.size(); ++i) { - newBlockValues.set(blockParamIds[i], blockParams[i]); + newBlockValues.set(blockParamIds[i], blockParams.get(i)); } dom::Object blockValuesOverlay = createFrame(newBlockValues, state.blockValues); @@ -3200,14 +3144,14 @@ renderBlock( // Block param paths if (optObj.exists("blockParamPaths")) { - dom::Value blockParamPathsV = optObj.find("blockParamPaths"); + dom::Value blockParamPathsV = optObj.get("blockParamPaths"); if (blockParamPathsV.isArray()) { dom::Array const& blockParamPaths = blockParamPathsV.getArray(); dom::Object newBlockValuePaths; for (std::size_t i = 0; i < blockParamIds.size(); ++i) { - newBlockValuePaths.set(blockParamIds[i], blockParamPaths[i]); + newBlockValuePaths.set(blockParamIds[i], blockParamPaths.get(i)); } dom::Object blockValuePathsOverlay = createFrame(newBlockValuePaths, state.blockValuePaths); @@ -3335,12 +3279,12 @@ renderBlock( bool const isStandaloneInvertedSection = tag.type == '^' && !isChainedBlock; if (isStandaloneInvertedSection) { - auto fnV = cb.find("fn"); - auto inverse = cb.find("inverse"); + auto fnV = cb.get("fn"); + auto inverse = cb.get("inverse"); cb.set("fn", inverse); cb.set("inverse", fnV); - auto fn_write = cb.find("write"); - auto write_inverse = cb.find("write_inverse"); + auto fn_write = cb.get("write"); + auto write_inverse = cb.get("write_inverse"); cb.set("write", write_inverse); cb.set("write_inverse", fn_write); } @@ -3348,12 +3292,12 @@ renderBlock( // ============================================================== // Call helper // ============================================================== - if (emulateMustache && args[0].isFunction()) + if (emulateMustache && args.get(0).isFunction()) { // When emulating mustache, if the first argument // is a function, we call this function before // passing it to blockHelperMissing - args.set(0, args[0](cb)); + args.set(0, args.get(0)(cb)); } state.inlinePartials.emplace_back(); // state.parentContext.emplace_back(context); @@ -3432,20 +3376,20 @@ if_fn(dom::Array const& arguments) return Unexpected(Error("#if requires exactly one argument")); } - dom::Value conditional = arguments[0]; - dom::Value options = arguments[1]; - dom::Value context = options["context"]; + dom::Value conditional = arguments.get(0); + dom::Value options = arguments.get(1); + dom::Value context = options.get("context"); if (conditional.isFunction()) { MRDOX_TRY(conditional, conditional.getFunction().try_invoke(context)); } - if ((!options["hash"]["includeZero"] && !conditional) || isEmpty(conditional)) { - MRDOX_TRY(options["write_inverse"].getFunction().try_invoke(context)); + if ((!options.lookup("hash.includeZero") && !conditional) || isEmpty(conditional)) { + MRDOX_TRY(options.get("write_inverse").getFunction().try_invoke(context)); } else { - MRDOX_TRY(options["write"].getFunction().try_invoke(context)); + MRDOX_TRY(options.get("write").getFunction().try_invoke(context)); } return {}; } @@ -3457,13 +3401,13 @@ unless_fn(dom::Array const& arguments) return Unexpected(Error("#unless requires exactly one argument")); } - dom::Value options = arguments[1]; - dom::Value fn = options["fn"]; - dom::Value inverse = options["inverse"]; + dom::Value options = arguments.get(1); + dom::Value fn = options.get("fn"); + dom::Value inverse = options.get("inverse"); options.set("fn", inverse); options.set("inverse", fn); - dom::Value write = options["write"]; - dom::Value write_inverse = options["write_inverse"]; + dom::Value write = options.get("write"); + dom::Value write_inverse = options.get("write_inverse"); options.set("write", write_inverse); options.set("write_inverse", write); dom::Array inv_arguments = arguments; @@ -3478,32 +3422,32 @@ with_fn(dom::Array const& arguments) return Unexpected(Error("#with requires exactly one argument")); } - dom::Value context = arguments[0]; - dom::Value options = arguments[1]; + dom::Value context = arguments.get(0); + dom::Value options = arguments.get(1); if (context.isFunction()) { - MRDOX_TRY(context, context.getFunction().try_invoke(options["context"])); + MRDOX_TRY(context, context.getFunction().try_invoke(options.get("context"))); } if (!isEmpty(context)) { - dom::Value data = options["data"]; - if (data && options["ids"]) + dom::Value data = options.get("data"); + if (data && options.get("ids")) { data = createFrame(data); data.set("contextPath", appendContextPath( - data["contextPath"], options["ids"][0])); + data.get("contextPath"), options.get("ids").get(0))); } dom::Array blockParams = {{context}}; - dom::Array blockParamPaths = {{data && data["contextPath"]}}; + dom::Array blockParamPaths = {{data && data.get("contextPath")}}; dom::Object cbOpt; cbOpt.set("data", data); cbOpt.set("blockParams", blockParams); cbOpt.set("blockParamPaths", blockParamPaths); - MRDOX_TRY(options["write"].getFunction().try_invoke(context, cbOpt)); + MRDOX_TRY(options.get("write").getFunction().try_invoke(context, cbOpt)); } else { - MRDOX_TRY(options["write_inverse"].getFunction().try_invoke(options["context"])); + MRDOX_TRY(options.get("write_inverse").getFunction().try_invoke(options.get("context"))); } return {}; } @@ -3516,23 +3460,23 @@ each_fn(dom::Value context, dom::Value const& options) return Unexpected(Error("Must pass iterator to #each")); } - dom::Value fn = options["write"]; - dom::Value inverse = options["write_inverse"]; + dom::Value fn = options.get("write"); + dom::Value inverse = options.get("write_inverse"); std::size_t i = 0; dom::Value data; std::string contextPath; - if (options["data"] && options["ids"]) { + if (options.get("data") && options.get("ids")) { contextPath = appendContextPath( - options["data"]["contextPath"], options["ids"][0]) + '.'; + options.lookup("data.contextPath"), options.get("ids").get(0)) + '.'; } if (context.isFunction()) { - MRDOX_TRY(context, context.getFunction().try_invoke(options["context"])); + MRDOX_TRY(context, context.getFunction().try_invoke(options.get("context"))); } - if (options["data"]) { - data = createFrame(options["data"]); + if (options.get("data")) { + data = createFrame(options.get("data")); } auto execIteration = [&context, &data, &contextPath, &fn]( @@ -3552,13 +3496,13 @@ each_fn(dom::Value context, dom::Value const& options) data.set("contextPath", contextPath + field); } - dom::Array blockParams = {{context[field], field}}; - dom::Array blockParamPaths = {{data && data["contextPath"], nullptr}}; + dom::Array blockParams = {{context.get(field), field}}; + dom::Array blockParamPaths = {{data && data.get("contextPath"), nullptr}}; dom::Object cbOpt; cbOpt.set("data", data); cbOpt.set("blockParams", blockParams); cbOpt.set("blockParamPaths", blockParamPaths); - return fn.getFunction().try_invoke(context[field], cbOpt); + return fn.getFunction().try_invoke(context.get(field), cbOpt); }; bool const isJSObject = static_cast( @@ -3577,7 +3521,8 @@ each_fn(dom::Value context, dom::Value const& options) else if (context.isObject()) { dom::Value priorKey; - for (auto const& [key, value] : context.getObject()) + auto exp = context.getObject().visit([&]( + dom::String const& key, dom::Value const& value) -> Expected { if (!priorKey.isUndefined()) { @@ -3585,6 +3530,11 @@ each_fn(dom::Value context, dom::Value const& options) } priorKey = key; ++i; + return {}; + }); + if (!exp) + { + return Unexpected(exp.error()); } if (!priorKey.isUndefined()) { @@ -3595,7 +3545,7 @@ each_fn(dom::Value context, dom::Value const& options) if (i == 0) { - MRDOX_TRY(inverse.getFunction().try_invoke(options["context"])); + MRDOX_TRY(inverse.getFunction().try_invoke(options.get("context"))); } return {}; } @@ -3607,7 +3557,7 @@ lookup_fn(dom::Value const& obj, dom::Value const& field, dom::Value const& opti { return obj; } - return options["lookupProperty"].getFunction().try_invoke(obj, field); + return options.get("lookupProperty").getFunction().try_invoke(obj, field); } Expected @@ -3619,22 +3569,22 @@ log_fn(dom::Array const& arguments) std::size_t const n = arguments.size(); for (std::size_t i = 0; i < n - 1; ++i) { - args.emplace_back(arguments[i]); + args.emplace_back(arguments.get(i)); } dom::Value level = 1; - dom::Value hash = options["hash"]; - dom::Value data = options["data"]; - if (hash.exists("level") && !hash["level"].isNull()) + dom::Value hash = options.get("hash"); + dom::Value data = options.get("data"); + if (hash.exists("level") && !hash.get("level").isNull()) { - level = options["hash"]["level"]; + level = options.lookup("hash.level"); } - else if (data.exists("level") && !data["level"].isNull()) + else if (data.exists("level") && !data.get("level").isNull()) { - level = options["data"]["level"]; + level = options.lookup("data.level"); } args.set(0, level); - MRDOX_TRY(options["log"].getFunction().call(args)); + MRDOX_TRY(options.get("log").getFunction().call(args)); return {}; } @@ -3648,7 +3598,7 @@ helper_missing_fn(dom::Array const& arguments) return Unexpected(Error(fmt::format( R"(Missing helper: "{}")", - arguments.back()["name"]))); + arguments.back().get("name")))); } Expected @@ -3657,42 +3607,42 @@ block_helper_missing_fn( { if (context == true) { - MRDOX_TRY(options["write"].getFunction().try_invoke(options["context"])); + MRDOX_TRY(options.get("write").getFunction().try_invoke(options.get("context"))); } else if ( context == false || context.isNull() || context.isUndefined()) { - MRDOX_TRY(options["write_inverse"].getFunction().try_invoke(options["context"])); + MRDOX_TRY(options.get("write_inverse").getFunction().try_invoke(options.get("context"))); } else if (context.isArray()) { if (!context.empty()) { - if (options["ids"]) + if (options.get("ids")) { - options.set("ids", dom::Array{{options["name"]}}); + options.set("ids", dom::Array{{options.get("name")}}); } MRDOX_TRY(each_fn(context, options)); } else { - MRDOX_TRY(options["write_inverse"].getFunction().try_invoke(options["context"])); + MRDOX_TRY(options.get("write_inverse").getFunction().try_invoke(options.get("context"))); } } else { dom::Object fnOpt; - if (options["data"] && options["ids"]) + if (options.get("data") && options.get("ids")) { - dom::Object data = createFrame(options["data"]); + dom::Object data = createFrame(options.get("data")); data.set( "contextPath", - appendContextPath(data["contextPath"], options["name"])); + appendContextPath(data.get("contextPath"), options.get("name"))); fnOpt.set("data", data); } - MRDOX_TRY(options["write"].getFunction().try_invoke(context, fnOpt)); + MRDOX_TRY(options.get("write").getFunction().try_invoke(context, fnOpt)); } return {}; } @@ -3730,7 +3680,7 @@ and_fn(dom::Array const& args) std::size_t const n = args.size(); for (std::size_t i = 0; i < n - 1; ++i) { - if (!args[i]) + if (!args.get(i)) { return false; } @@ -3743,7 +3693,7 @@ or_fn(dom::Array const& args) { std::size_t const n = args.size(); for (std::size_t i = 0; i < n - 1; ++i) { - if (args[i]) + if (args.get(i)) { return true; } @@ -3757,11 +3707,11 @@ eq_fn(dom::Array const& args) { { return true; } - dom::Value first = args[0]; + dom::Value first = args.get(0); std::size_t const n = args.size(); for (std::size_t i = 1; i < n - 1; ++i) { - if (first != args[i]) + if (first != args.get(i)) { return false; } @@ -3774,32 +3724,12 @@ ne_fn(dom::Array const& args) { return !eq_fn(args); } -std::string_view -kindToString(dom::Kind kind) { - switch (kind) { - case dom::Kind::Null: - return "null"; - case dom::Kind::Object: - return "object"; - case dom::Kind::Array: - return "array"; - case dom::Kind::String: - return "string"; - case dom::Kind::Integer: - return "integer"; - case dom::Kind::Boolean: - return "boolean"; - default: - MRDOX_UNREACHABLE(); - } -} - bool not_fn(dom::Array const& args) { std::size_t const n = args.size(); for (std::size_t i = 0; i < n - 1; ++i) { - if (!args[i]) + if (!args.get(i)) { return true; } @@ -3867,12 +3797,12 @@ relativize_fn(dom::Value to, dom::Value from, dom::Value context) { // NOTE only legacy invocation provides both to and from context = from; - from = context["data"]["root"]["page"]["url"]; + from = context.lookup("data.root.page.url"); } if (!from) { - dom::Value sitePath = context["data"]["root"]["site"]["path"]; + dom::Value sitePath = context.lookup("data.root.site.path"); if (sitePath) { return sitePath + to; @@ -3953,12 +3883,12 @@ normalize_index(std::int64_t i, std::int64_t n) { dom::Value at_fn(dom::Value range, dom::Value field, dom::Value options) { - auto isBlock = options.isUndefined() && static_cast(field["fn"]); + auto isBlock = options.isUndefined() && static_cast(field.get("fn")); if (isBlock) { options = field; field = range; - range = options["fn"](); + range = options.get("fn")(); } std::int64_t index = 0; @@ -3989,7 +3919,7 @@ at_fn(dom::Value range, dom::Value field, dom::Value options) std::string key(field.getString().get()); if (obj.exists(key)) { - return obj.find(key); + return obj.get(key); } return nullptr; } @@ -4006,13 +3936,13 @@ concat_fn( dom::Value range2, dom::Value options) { - auto isBlock = options.isUndefined() && static_cast(range2["fn"]); + auto isBlock = options.isUndefined() && static_cast(range2.get("fn")); if (isBlock) { options = range2; range2 = sep; sep = range1; - range1 = options["fn"](); + range1 = options.get("fn")(); } if (range1.isString() || range2.isString()) @@ -4048,10 +3978,10 @@ count_fn(dom::Array const& arguments) { std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); auto const isBlock = static_cast(fn); - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const stringOverload = (isBlock && firstArg.isString()) || (firstArg.isString() && secondArg.isString()); @@ -4068,24 +3998,24 @@ count_fn(dom::Array const& arguments) end = static_cast(str.size()); if (n > 2) { - start = arguments[1].getInteger(); + start = arguments.get(1).getInteger(); if (n > 3) { - end = arguments[2].getInteger(); + end = arguments.get(2).getInteger(); } } } else /* !isBlock */ { - str = arguments[0].getString(); - sub = arguments[1].getString(); + str = arguments.get(0).getString(); + sub = arguments.get(1).getString(); end = static_cast(str.size()); if (n > 3) { - start = arguments[2].getInteger(); + start = arguments.get(2).getInteger(); if (n > 4) { - end = arguments[3].getInteger(); + end = arguments.get(3).getInteger(); } } } @@ -4104,8 +4034,8 @@ count_fn(dom::Array const& arguments) else { // Generic range overload - dom::Value range = arguments[0]; - dom::Value item = arguments[1]; + dom::Value range = arguments.get(0); + dom::Value item = arguments.get(1); if (range.isString()) { // String overload: find chars in it @@ -4124,13 +4054,12 @@ count_fn(dom::Array const& arguments) // Object overload: find values in it dom::Object const& obj = range.getObject(); std::int64_t count = 0; - for (auto const& [key, val] : obj) - { + obj.visit([&](dom::String const&, dom::Value const& val) { if (val == item) { ++count; } - } + }); return count; } else @@ -4146,9 +4075,9 @@ replace_fn(dom::Array const& arguments) { std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); bool const stringOverload = (isBlock && firstArg.isString()) || @@ -4248,13 +4177,12 @@ replace_fn(dom::Array const& arguments) { // Object overload: replace values in it dom::Object obj = createFrame(range.getObject()); - for (auto const& [key, val] : obj) - { + obj.visit([&](dom::String const& key, dom::Value const& val) { if (val == item) { obj.set(key, replacement); } - } + }); return obj; } else @@ -4284,8 +4212,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4310,9 +4238,9 @@ registerStringHelpers(Handlebars& hbs) char fillchar = ' '; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4349,9 +4277,9 @@ registerStringHelpers(Handlebars& hbs) std::string fill = " "; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4399,9 +4327,9 @@ registerStringHelpers(Handlebars& hbs) std::string fill = " "; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4452,9 +4380,9 @@ registerStringHelpers(Handlebars& hbs) std::int64_t end = 0; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4499,9 +4427,9 @@ registerStringHelpers(Handlebars& hbs) std::int64_t end = 0; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); auto const isBlock = static_cast(fn); if (isBlock) { @@ -4544,9 +4472,9 @@ registerStringHelpers(Handlebars& hbs) std::int64_t tabsize = 8; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4589,9 +4517,9 @@ registerStringHelpers(Handlebars& hbs) std::int64_t end = 0; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4648,9 +4576,9 @@ registerStringHelpers(Handlebars& hbs) std::int64_t end = 0; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4704,8 +4632,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4727,8 +4655,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4749,8 +4677,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4770,8 +4698,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4794,8 +4722,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4815,8 +4743,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4836,8 +4764,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4857,8 +4785,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4878,8 +4806,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4917,8 +4845,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4940,8 +4868,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -4963,8 +4891,8 @@ registerStringHelpers(Handlebars& hbs) { std::string res; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5001,9 +4929,9 @@ registerStringHelpers(Handlebars& hbs) std::string str; dom::Array arr; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5041,9 +4969,9 @@ registerStringHelpers(Handlebars& hbs) std::string chars = " \t\r\n"; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5078,9 +5006,9 @@ registerStringHelpers(Handlebars& hbs) std::string chars = " \t\r\n"; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5116,9 +5044,9 @@ registerStringHelpers(Handlebars& hbs) std::string chars = " \t\r\n"; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5152,9 +5080,9 @@ registerStringHelpers(Handlebars& hbs) std::string str; std::string sep; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5189,9 +5117,9 @@ registerStringHelpers(Handlebars& hbs) std::string str; std::string sep; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5226,9 +5154,9 @@ registerStringHelpers(Handlebars& hbs) std::string str; std::string prefix; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5257,9 +5185,9 @@ registerStringHelpers(Handlebars& hbs) std::string str; std::string suffix; dom::Value options = arguments.back(); - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; - dom::Value fn = options["fn"]; + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5292,9 +5220,9 @@ registerStringHelpers(Handlebars& hbs) std::int64_t maxsplit = -1; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5351,9 +5279,9 @@ registerStringHelpers(Handlebars& hbs) std::int64_t maxsplit = -1; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; - dom::Value firstArg = arguments[0]; - dom::Value secondArg = arguments[1]; + dom::Value fn = options.get("fn"); + dom::Value firstArg = arguments.get(0); + dom::Value secondArg = arguments.get(1); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5410,7 +5338,7 @@ registerStringHelpers(Handlebars& hbs) bool keepends = false; std::size_t const n = arguments.size(); dom::Value options = arguments.back(); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); dom::Value firstArg = arguments.at(0); dom::Value secondArg = arguments.at(1); bool const isBlock = static_cast(fn); @@ -5462,7 +5390,7 @@ registerStringHelpers(Handlebars& hbs) dom::Value options = arguments.back(); dom::Value firstArg = arguments.at(0); dom::Value secondArg = arguments.at(1); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5498,7 +5426,7 @@ registerStringHelpers(Handlebars& hbs) dom::Value options = arguments.back(); dom::Value firstArg = arguments.at(0); dom::Value secondArg = arguments.at(1); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5530,7 +5458,7 @@ registerStringHelpers(Handlebars& hbs) dom::Value options = arguments.back(); dom::Value firstArg = arguments.at(0); dom::Value secondArg = arguments.at(1); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5553,7 +5481,7 @@ registerStringHelpers(Handlebars& hbs) dom::Value options = arguments.back(); dom::Value firstArg = arguments.at(0); dom::Value secondArg = arguments.at(1); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5597,7 +5525,7 @@ registerStringHelpers(Handlebars& hbs) std::string res; dom::Value options = arguments.back(); dom::Value firstArg = arguments.at(0); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5626,7 +5554,7 @@ registerStringHelpers(Handlebars& hbs) std::string res; dom::Value options = arguments.back(); dom::Value firstArg = arguments.at(0); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); bool const isBlock = static_cast(fn); if (isBlock) { @@ -5703,10 +5631,10 @@ registerContainerHelpers(Handlebars& hbs) } auto const& obj = container.getObject(); dom::Array res; - for (auto const& [key, _]: obj) + obj.visit([&res](auto const& key, auto const&) { res.emplace_back(key); - } + }); return res; }); @@ -5723,10 +5651,10 @@ registerContainerHelpers(Handlebars& hbs) } auto const& obj = container.getObject(); dom::Array res; - for (auto const& [_, value]: obj) + obj.visit([&res](auto const&, auto const& value) { res.emplace_back(value); - } + }); return res; }); @@ -5742,7 +5670,7 @@ registerContainerHelpers(Handlebars& hbs) auto const n = static_cast(arr.size()); for (std::int64_t i = 0; i < n; i++) { - if (arr[i] != val) + if (arr.get(i) != val) { res.emplace_back(arr.at(i)); } @@ -5754,13 +5682,13 @@ registerContainerHelpers(Handlebars& hbs) auto const& obj = range.getObject(); auto const& key = item.getString(); dom::Object res; - for (auto const& [k, v]: obj) + obj.visit([&res, &key](auto const& k, auto const& v) { if (k != key) { res.set(k, v); } - } + }); return res; } else @@ -5822,7 +5750,7 @@ registerContainerHelpers(Handlebars& hbs) std::size_t const n = arr.size(); for (std::size_t i = 0; i < n; ++i) { - if (arr[i] == value) + if (arr.get(i) == value) { return true; } @@ -5846,7 +5774,7 @@ registerContainerHelpers(Handlebars& hbs) auto const n = static_cast(keys.size()); for (std::int64_t i = 0; i < n; ++i) { - dom::Value k = keys[i]; + dom::Value k = keys.get(i); if (obj.exists(k.getString())) { return true; @@ -5864,8 +5792,8 @@ registerContainerHelpers(Handlebars& hbs) std::size_t const n2 = arr.size(); for (std::size_t j = 0; j < n2; ++j) { - dom::Value a = arr[j]; - dom::Value b = values[i]; + dom::Value a = arr.get(j); + dom::Value b = values.get(i); if (a == b) { return true; @@ -5916,7 +5844,7 @@ registerContainerHelpers(Handlebars& hbs) auto const& key = field.getString(); if (obj.exists(key)) { - return obj.find(key); + return obj.get(key); } return default_value; } @@ -5936,13 +5864,13 @@ registerContainerHelpers(Handlebars& hbs) { auto const& obj = items.getObject(); dom::Array res; - for (auto const& [key, value]: obj) + obj.visit([&res](auto const& key, auto const& value) { dom::Array item; item.emplace_back(key); item.emplace_back(value); res.emplace_back(item); - } + }); return res; } else @@ -5970,9 +5898,15 @@ registerContainerHelpers(Handlebars& hbs) auto const& obj = range.getObject(); if (obj.empty()) { - return nullptr; + return dom::Kind::Undefined; } - return obj.get(0).value; + dom::Value res; + obj.visit([&](dom::String const&, dom::Value const& value) -> bool + { + res = value; + return false; + }); + return res; } else { @@ -6001,9 +5935,14 @@ registerContainerHelpers(Handlebars& hbs) auto const& obj = range.getObject(); if (obj.empty()) { - return nullptr; + return dom::Kind::Undefined; } - return obj.get(obj.size() - 1).value; + dom::Value res; + obj.visit([&](dom::String const&, dom::Value const& value) + { + res = value; + }); + return res; } else { @@ -6032,13 +5971,13 @@ registerContainerHelpers(Handlebars& hbs) { auto const& obj = container.getObject(); dom::Array res; - for (auto const& [key, value]: obj) + obj.visit([&res](dom::String const& key, dom::Value const& value) { dom::Array item; item.emplace_back(key); item.emplace_back(value); res.emplace_back(item); - } + }); dom::Array reversed; for (std::size_t i = res.size(); i > 0; --i) { reversed.emplace_back(res.at(i - 1)); @@ -6061,10 +6000,10 @@ registerContainerHelpers(Handlebars& hbs) auto const& obj = container.getObject(); auto const& other = items.getObject(); dom::Object res = createFrame(obj); - for (auto const& [k, v]: other) + other.visit([&res](dom::String const& k, dom::Value const& v) { res.set(k, v); - } + }); return res; } else if (container.isArray()) @@ -6180,7 +6119,7 @@ registerContainerHelpers(Handlebars& hbs) } // If the value is an object and the key exists, then we // sort it by the value at the key - return a.getObject().find(key) < b.getObject().find(key); + return a.getObject().get(key) < b.getObject().get(key); }); return dom::Array(res); } @@ -6286,19 +6225,18 @@ registerContainerHelpers(Handlebars& hbs) { auto const& obj = range.getObject(); dom::Array res; - std::size_t i = 0; - std::size_t const n = obj.size(); - while (i < n) + dom::Object chunk; + obj.visit([&](dom::String const& k, dom::Value const& v) { - dom::Object chunk; - std::int64_t j = 0; - while (j < chunkSize && i < n) + chunk.set(k, v); + if (std::cmp_greater_equal(chunk.size(), chunkSize)) { - auto const& [k, v] = obj.at(i); - chunk.set(k, v); - ++i; - ++j; + res.emplace_back(chunk); + chunk = dom::Object(); } + }); + if (!chunk.empty()) + { res.emplace_back(chunk); } return res; @@ -6326,7 +6264,7 @@ registerContainerHelpers(Handlebars& hbs) dom::Object res; for (std::size_t i = 0; i < n; ++i) { - if (copied[i] != 0x00 || !array[i].isObject() || !array[i].getObject().exists(key)) + if (copied[i] != 0x00 || !array.get(i).isObject() || !array.get(i).getObject().exists(key)) { // object already copied or doesn't have the key copied[i] = 0x01; @@ -6335,9 +6273,9 @@ registerContainerHelpers(Handlebars& hbs) copied[i] = 0x01; // Create a group for this key value - std::string group_name(toString(array[i][key])); + std::string group_name(toString(array.get(i).get(key))); dom::Array group; - group.emplace_back(array[i]); + group.emplace_back(array.get(i)); // Copy any other equivalent keys to the same group for (std::size_t j = i; j < n; ++j) @@ -6346,9 +6284,9 @@ registerContainerHelpers(Handlebars& hbs) { continue; } - if (array[j][key].getString() == array[i][key].getString()) + if (array.get(j).get(key).getString() == array.get(i).get(key).getString()) { - group.emplace_back(array[j]); + group.emplace_back(array.get(j)); copied[j] = 0x01; } } @@ -6371,9 +6309,9 @@ registerContainerHelpers(Handlebars& hbs) auto n = static_cast(range.size()); for (std::int64_t i = 0; i < n; ++i) { - if (range[i].isObject() && range[i].getObject().exists(key)) + if (range.get(i).isObject() && range.get(i).getObject().exists(key)) { - res.emplace_back(range[i].getObject().find(key)); + res.emplace_back(range.get(i).getObject().get(key)); } } return res; diff --git a/src/lib/Support/JavaScript.cpp b/src/lib/Support/JavaScript.cpp index 789c69058..f74eb7b1c 100644 --- a/src/lib/Support/JavaScript.cpp +++ b/src/lib/Support/JavaScript.cpp @@ -329,7 +329,7 @@ struct ObjectProxy : ObjectBase static void push(Access& A, dom::Object const& obj); }; -#if 1 +#if 0 using ArrayRep = ArrayProxy; //ArrayGetSet; using ObjectRep = ObjectGetSet; #else @@ -559,15 +559,15 @@ push( duk_set_finalizer(A, idx); std::construct_at(&obj_, obj); - for(auto const& kv : obj) + obj.visit([&](dom::String const& key, dom::Value const&) { - dukM_push_string(A, kv.key); + dukM_push_string(A, key); // Method: Getter // Effects: return obj[key] // Signature: (key) duk_push_c_function(A, - [](duk_context* ctx) -> duk_ret_t + [](duk_context* ctx) -> duk_ret_t { Access A(ctx); auto key = dukM_get_string(A, 0); @@ -576,13 +576,13 @@ push( // duk_pop_n would invalidate it dom::Object obj = *get(A, 1); duk_pop_n(A, duk_get_top(A)); - domValue_push(A, obj.find(key)); + domValue_push(A, obj.get(key)); return 1; }, 1); duk_def_prop(A, idx, - DUK_DEFPROP_HAVE_GETTER | - DUK_DEFPROP_SET_ENUMERABLE); - } + DUK_DEFPROP_HAVE_GETTER | + DUK_DEFPROP_SET_ENUMERABLE); + }); } void @@ -624,10 +624,10 @@ push( [](duk_context* ctx) -> duk_ret_t { Access A(ctx); + std::string_view key = dukM_get_string(A, 1); duk_push_this(A); // the proxy - auto& obj = *get(A, -1); - auto key = dukM_get_string(A, 1); - auto const& v = obj.find(key); + dom::Object& obj = *get(A, -1); + dom::Value const& v = obj.get(key); duk_pop_n(A, duk_get_top(A)); domValue_push(A, v); return 1; @@ -642,9 +642,9 @@ push( { Access A(ctx); duk_push_this(A); - auto& obj = *get(A, -1); - auto key = dukM_get_string(A, 1); - auto const& v = obj.find(key); + dom::Object& obj = *get(A, -1); + std::string_view key = dukM_get_string(A, 1); + dom::Value const& v = obj.get(key); duk_pop_n(A, duk_get_top(A)); // VFALCO should add dom::Object::exists(k) for this duk_push_boolean(A, ! v.isNull()); @@ -652,7 +652,6 @@ push( }, 3); dukM_put_prop_string(A, -2, "has"); -#if 1 // Trap: [[OwnPropertyKeys]] // Effects: return range(Object()) // Signature: () @@ -661,15 +660,15 @@ push( { Access A(ctx); duk_push_this(A); - auto& obj = *get(A, -1); + dom::Object obj = *get(A, -1); duk_pop(A); duk_push_array(A); - for(auto const& kv : obj) + duk_uarridx_t i = 0; + obj.visit([&](dom::String const& key, dom::Value const& value) { - dukM_push_string(A, kv.key); - domValue_push(A, kv.value); - duk_put_prop(A, -3); - } + dukM_push_string(A, key); + duk_put_prop_index(A, -2, i++); + }); return 1; }, 0); dukM_put_prop_string(A, -2, "ownKeys"); @@ -713,17 +712,48 @@ push( duk_push_c_function(A, [](duk_context* ctx) -> duk_ret_t { return 0; }, 0); dukM_put_prop_string(A, -2, "getOwnPropertyDescriptor"); -#endif duk_push_proxy(A, 0); } //------------------------------------------------ // -// Value +// dom::Value // //------------------------------------------------ +namespace { + +class NativeObjectImpl : public dom::ObjectImpl +{ + Scope& scope_; + duk_idx_t idx_; + +public: + NativeObjectImpl( + Scope& scope, duk_idx_t idx) noexcept + : scope_(scope) + , idx_(idx) + { + } + + std::size_t size() const override + { + return 0; + } + + dom::Value get(std::string_view) const override + { + return nullptr; + } + + void set(dom::String, dom::Value) override + { + } +}; + +} // (anon) + // return a dom::Value from a stack element static dom::Value @@ -763,6 +793,13 @@ domValue_get( } if (duk_is_object(A, idx)) { + // Replace with NativeObjectImpl + // auto const obj = ObjectRep::get(A, idx); + // if(obj) + // { + // return *obj; + // } + // return dom::newObject(scope, idx); dom::Object res; duk_enum(A, idx, DUK_ENUM_OWN_PROPERTIES_ONLY); while (duk_next(A, -1, 1)) diff --git a/src/lib/Support/Lua.cpp b/src/lib/Support/Lua.cpp index 726e095a1..537dba3e4 100644 --- a/src/lib/Support/Lua.cpp +++ b/src/lib/Support/Lua.cpp @@ -431,7 +431,7 @@ domObject_push_metatable( { Access A(L); domValue_push(A, - domObject_get(A, 1).find( + domObject_get(A, 1).get( luaM_getstring(A, 2))); lua_replace(A, 1); return 1; @@ -512,9 +512,18 @@ domObject_push_metatable( return 2; } auto index = lua_tonumber(A, lua_upvalueindex(1)); - auto const& kv = obj[index]; - luaM_pushstring(A, kv.key); - domValue_push(A, kv.value); + // Visit obj and get the index-th element and key + std::size_t i = 0; + obj.visit([&](dom::String key, dom::Value value) + { + if (i == index) { + luaM_pushstring(A, key); + domValue_push(A, value); + return false; + } + ++i; + return true; + }); ++index; if(index < obj.size()) lua_pushnumber(A, index); diff --git a/src/test/lib/Dom/Dom.cpp b/src/test/lib/Dom/Dom.cpp index 36cde523b..f6b68170c 100644 --- a/src/test/lib/Dom/Dom.cpp +++ b/src/test/lib/Dom/Dom.cpp @@ -17,8 +17,2141 @@ namespace dom { struct Dom_test { + void + kind_test() + { + BOOST_TEST(toString(Kind::Undefined) == "undefined"); + BOOST_TEST(toString(Kind::Null) == "null"); + BOOST_TEST(toString(Kind::Boolean) == "boolean"); + BOOST_TEST(toString(Kind::Integer) == "integer"); + BOOST_TEST(toString(Kind::String) == "string"); + BOOST_TEST(toString(Kind::SafeString) == "safeString"); + BOOST_TEST(toString(Kind::Array) == "array"); + BOOST_TEST(toString(Kind::Object) == "object"); + BOOST_TEST(toString(Kind::Function) == "function"); + BOOST_TEST(toString(static_cast(123)) == "unknown"); + } + + void + string_test() + { + // String() + { + String s; + BOOST_TEST(s.empty()); + } + + // String(String&&) + { + String s1("hello"); + String s2(std::move(s1)); + BOOST_TEST(s2 == "hello"); + } + + // String(String const&) + { + String s1("hello"); + String s2(s1); + BOOST_TEST(s2 == "hello"); + BOOST_TEST(s1 == "hello"); + } + + // String(std::string_view s) + { + String s(std::string_view("hello")); + BOOST_TEST(s == "hello"); + } + + // String(StringLike const& s) + { + String s(std::string("hello")); + BOOST_TEST(s == "hello"); + } + + // String(char const(&psz)[N]) + { + String s("hello"); + BOOST_TEST(s == "hello"); + } + + // operator=(String&&) + { + String s1("hello"); + String s2; + s2 = std::move(s1); + BOOST_TEST(s2 == "hello"); + } + + // operator=(String const&) + { + String s1("hello"); + String s2; + s2 = s1; + BOOST_TEST(s2 == "hello"); + BOOST_TEST(s1 == "hello"); + } + + // empty() + { + String s; + BOOST_TEST(s.empty()); + s = "hello"; + BOOST_TEST(! s.empty()); + } + + // std::string_view get() const + { + String s("hello"); + BOOST_TEST(s.get() == "hello"); + } + + // operator std::string_view() + { + String s("hello"); + std::string_view sv(s); + BOOST_TEST(sv == "hello"); + } + + // std::string str() + { + String s("hello"); + BOOST_TEST(s.str() == "hello"); + } + + // std::size_t size() const + { + String s("hello"); + BOOST_TEST(s.size() == 5); + } + + // char const* data() const + { + String s("hello"); + BOOST_TEST(s.data() == s.get().data()); + } + + // char const* c_str() const + { + String s("hello"); + BOOST_TEST(s.c_str() == s.get().data()); + } + + // swap(String&) + { + String s1("hello"); + String s2("world"); + s1.swap(s2); + BOOST_TEST(s1 == "world"); + BOOST_TEST(s2 == "hello"); + } + + // swap(String&, String&) + { + String s1("hello"); + String s2("world"); + swap(s1, s2); + BOOST_TEST(s1 == "world"); + BOOST_TEST(s2 == "hello"); + } + + // operator==(String const& lhs, StringLike const& rhs) + { + String s1("hello"); + std::string s2("hello"); + BOOST_TEST(s1 == s2); + BOOST_TEST(s2 == s1); + } + + // operator!=(String const& lhs, StringLike const& rhs) + { + String s1("hello"); + std::string s2("hello"); + BOOST_TEST(!(s1 != s2)); + BOOST_TEST(!(s2 != s1)); + } + + // auto operator<=>(String const& lhs, StringLike const& rhs) + { + String s1("hello"); + std::string s2("hello"); + BOOST_TEST((s1 <=> s2) == std::strong_ordering::equal); + BOOST_TEST((s2 <=> s1) == std::strong_ordering::equal); + } + + // operator==(String const& lhs, String const& rhs) + { + String s1("hello"); + String s2("hello"); + BOOST_TEST(s1 == s2); + BOOST_TEST(s2 == s1); + } + + // operator!=(String const& lhs, String const& rhs) + { + String s1("hello"); + String s2("hello"); + BOOST_TEST(!(s1 != s2)); + BOOST_TEST(!(s2 != s1)); + } + + // auto operator<=>(String const& lhs, String const& rhs) + { + String s1("hello"); + String s2("hello"); + BOOST_TEST((s1 <=> s2) == std::strong_ordering::equal); + BOOST_TEST((s2 <=> s1) == std::strong_ordering::equal); + } + + // auto operator+(String const& lhs, String const& rhs) + { + String s1("hello"); + String s2("world"); + String s3 = s1 + s2; + BOOST_TEST(s3 == "helloworld"); + } + + // operator+(S const& lhs, String const& rhs) + { + String s1("hello"); + std::string s2("world"); + String s3 = s2 + s1; + BOOST_TEST(s3 == "worldhello"); + } + + // operator+(String const& lhs, S const& rhs) + { + String s1("hello"); + std::string s2("world"); + String s3 = s1 + s2; + BOOST_TEST(s3 == "helloworld"); + } + + // fmt::formatter + { + String s("hello"); + BOOST_TEST(fmt::format("{}", s) == "hello"); + } + } + + void + array_test() + { + // Array() + { + Array a; + BOOST_TEST(a.empty()); + } + + // Array(Array&&) + { + Array a1; + a1.emplace_back("hello"); + Array a2(std::move(a1)); + BOOST_TEST(a2.size() == 1); + } + + // Array(Array const&) + { + Array a1; + a1.emplace_back("hello"); + Array a2(a1); + BOOST_TEST(a2.size() == 1); + BOOST_TEST(a1.size() == 1); + } + + // Array(impl_type impl) + { + auto impl = std::make_shared(); + impl->emplace_back("hello"); + Array a(impl); + a.emplace_back("world"); + BOOST_TEST(a.size() == 2); + } + + // Array(storage_type) + { + Array::storage_type v; + v.emplace_back("hello"); + Array a(v); + a.emplace_back("world"); + BOOST_TEST(a.size() == 2); + } + + // operator=(Array&&) + { + Array a1; + a1.emplace_back("hello"); + Array a2; + a2 = std::move(a1); + BOOST_TEST(a2.size() == 1); + } + + // operator=(Array const&) + { + Array a1; + a1.emplace_back("hello"); + Array a2; + a2 = a1; + BOOST_TEST(a2.size() == 1); + BOOST_TEST(a1.size() == 1); + } + + // auto impl() const + { + { + Array a; + a.emplace_back("hello"); + BOOST_TEST(a.impl()->size() == 1); + } + + { + auto impl = std::make_shared(); + impl->emplace_back("hello"); + Array a(impl); + a.emplace_back("world"); + BOOST_TEST(a.impl()->size() == 2); + BOOST_TEST(impl->size() == 2); + } + } + + // char const* type_key() + { + Array a; + BOOST_TEST(std::string_view(a.type_key()) == "Array"); + } + + // bool empty() + { + Array a; + BOOST_TEST(a.empty()); + a.emplace_back("hello"); + BOOST_TEST(! a.empty()); + } + + // size_type size() + { + Array a; + BOOST_TEST(a.empty()); + a.emplace_back("hello"); + BOOST_TEST(a.size() == 1); + } + + // void set(size_type i, Value v); + { + Array a; + a.emplace_back("hello"); + a.set(0, "world"); + BOOST_TEST(a.get(0) == "world"); + } + + // value_type get(size_type i) + // value_type at(size_type i) + { + Array a; + a.emplace_back("hello"); + BOOST_TEST(a.get(0) == "hello"); + BOOST_TEST(a.at(0) == "hello"); + } + + // value_type front() + { + Array a; + a.emplace_back("hello"); + BOOST_TEST(a.front() == "hello"); + } + + // value_type back() + { + Array a; + a.emplace_back("hello"); + BOOST_TEST(a.back() == "hello"); + } + + // iterator begin() + // iterator end() + { + Array a; + a.emplace_back("hello"); + BOOST_TEST(*a.begin() == "hello"); + BOOST_TEST(a.begin() != a.end()); + } + + // void push_back(value_type value) + { + Array a; + a.push_back("hello"); + BOOST_TEST(a.size() == 1); + BOOST_TEST(a.at(0) == "hello"); + } + + // template< class... Args > + // void emplace_back(Args&&... args) + { + Array a; + a.emplace_back("hello"); + BOOST_TEST(a.size() == 1); + BOOST_TEST(a.at(0) == "hello"); + } + + // friend Array operator+(Array const& lhs, Array const& rhs); + { + Array a1; + a1.emplace_back("hello"); + Array a2; + a2.emplace_back("world"); + Array a3 = a1 + a2; + BOOST_TEST(a3.size() == 2); + BOOST_TEST(a3.get(0) == "hello"); + BOOST_TEST(a3.get(1) == "world"); + } + + // template S> + // friend auto operator+(S const& lhs, Array const& rhs) + { + Array a1; + a1.emplace_back("hello"); + std::vector a2; + a2.emplace_back("world"); + Array a3 = a2 + a1; + BOOST_TEST(a3.size() == 2); + BOOST_TEST(a3.get(0) == "world"); + BOOST_TEST(a3.get(1) == "hello"); + Array a4 = a1 + a2; + BOOST_TEST(a4.size() == 2); + BOOST_TEST(a4.get(0) == "hello"); + BOOST_TEST(a4.get(1) == "world"); + } + + // void swap(Array& other) noexcept + { + Array a1; + a1.emplace_back("hello"); + Array a2; + a2.emplace_back("world"); + a1.swap(a2); + BOOST_TEST(a1.size() == 1); + BOOST_TEST(a2.size() == 1); + BOOST_TEST(a1.get(0) == "world"); + BOOST_TEST(a2.get(0) == "hello"); + } + + // friend void swap(Array& lhs, Array& rhs) noexcept + { + Array a1; + a1.emplace_back("hello"); + Array a2; + a2.emplace_back("world"); + swap(a1, a2); + BOOST_TEST(a1.size() == 1); + BOOST_TEST(a2.size() == 1); + BOOST_TEST(a1.get(0) == "world"); + BOOST_TEST(a2.get(0) == "hello"); + } + + // operator==(Array const&, Array const&) + { + Array a1; + a1.emplace_back("hello"); + Array a2; + a2.emplace_back("hello"); + BOOST_TEST(a1 == a2); + BOOST_TEST(a2 == a1); + a1 = a2; + BOOST_TEST(a1 == a2); + BOOST_TEST(a2 == a1); + } + + // operator<=>(Array const&, Array const&) + { + Array a1; + a1.emplace_back("hello"); + Array a2; + a2.emplace_back("hello"); + BOOST_TEST((a1 <=> a2) == std::strong_ordering::equal); + BOOST_TEST((a2 <=> a1) == std::strong_ordering::equal); + } + + // toString(Array const&); + { + // Behave same as JS: + // x = ['hello'] + // x.toString() == 'hello' + Array a; + BOOST_TEST(toString(a).empty()); + a.emplace_back("hello"); + BOOST_TEST(toString(a) == "hello"); + a.emplace_back("world"); + BOOST_TEST(toString(a) == "hello,world"); + } + } + + void + object_test() + { + // Object() + { + Object o; + BOOST_TEST(o.empty()); + } + + // Object(Object&&) + { + Object o1; + o1.set("hello", "world"); + Object o2(std::move(o1)); + BOOST_TEST(o2.size() == 1); + } + + // Object(Object const&) + { + Object o1; + o1.set("hello", "world"); + Object o2(o1); + BOOST_TEST(o2.size() == 1); + BOOST_TEST(o1.size() == 1); + } + + // Object(impl_type impl) + { + auto impl = std::make_shared(); + impl->set("hello", "world"); + Object o(impl); + o.set("goodbye", "world"); + BOOST_TEST(o.size() == 2); + } + + // explicit Object(storage_type list); + { + // explicit + { + Object::storage_type v; + v.emplace_back("hello", "world"); + Object o(v); + o.set("goodbye", "world"); + BOOST_TEST(o.size() == 2); + } + + // convertible from initializer_list + { + Object obj({ + { "a", 1 }, + { "b", nullptr }, + { "c", "test" }}); + BOOST_TEST(obj.size() == 3); + BOOST_TEST(obj.get("a") == 1); + BOOST_TEST(obj.get("b").isNull()); + BOOST_TEST(obj.get("c") == "test"); + } + } + + // Object& operator=(Object&&) + { + Object o1; + o1.set("hello", "world"); + Object o2; + o2 = std::move(o1); + BOOST_TEST(o2.size() == 1); + } + + // Object& operator=(Object const&) + { + Object o1; + o1.set("hello", "world"); + Object o2; + o2 = o1; + BOOST_TEST(o2.size() == 1); + BOOST_TEST(o1.size() == 1); + } + + // auto impl() const + { + { + Object o; + o.set("hello", "world"); + BOOST_TEST(o.impl()->size() == 1); + } + + { + auto impl = std::make_shared(); + impl->set("hello", "world"); + Object o(impl); + o.set("goodbye", "world"); + BOOST_TEST(o.impl()->size() == 2); + BOOST_TEST(impl->size() == 2); + } + } + + // char const* type_key() const noexcept + { + Object o; + BOOST_TEST(std::string_view(o.type_key()) == "Object"); + } + + // bool empty() const + { + Object o; + BOOST_TEST(o.empty()); + o.set("hello", "world"); + BOOST_TEST(! o.empty()); + } + + // size_type size() const + { + Object o; + BOOST_TEST(o.size() == 0); // NOLINT(*-container-size-empty) + o.set("hello", "world"); + BOOST_TEST(o.size() == 1); + } + + // get(std::string_view) + // at(std::string_view) + { + Object o; + o.set("hello", "world"); + BOOST_TEST(o.get("hello") == "world"); + BOOST_TEST(o.at("hello") == "world"); + } + + // exists(std::string_view) + { + Object o; + o.set("hello", "world"); + BOOST_TEST(o.exists("hello")); + BOOST_TEST(! o.exists("goodbye")); + } + + // set(std::string_view, Value) + { + Object o; + o.set("hello", "world"); + BOOST_TEST(o.get("hello") == "world"); + } + + // visit(F&& fn) + { + // return void + { + Object o; + o.set("hello", "world1"); + o.set("goodbye", "world2"); + o.visit([](String key, Value value) + { + BOOST_TEST((key == "hello" || key == "goodbye")); + BOOST_TEST((value == "world1" || value == "world2")); + }); + } + + // return bool + { + Object o; + o.set("hello", "world"); + o.set("goodbye", "world"); + int count = 0; + bool exp = o.visit([&count](String key, Value value) + { + BOOST_TEST(key == "hello"); + BOOST_TEST(value == "world"); + ++count; + return false; + }); + BOOST_TEST(!exp); + BOOST_TEST(count == 1); + } + + // return Expected + { + Object o; + o.set("hello", "world"); + o.set("goodbye", "world"); + int count = 0; + auto exp = o.visit([&count](String key, Value value) -> Expected + { + BOOST_TEST(key == "hello"); + BOOST_TEST(value == "world"); + ++count; + return Unexpected(Error("error")); + }); + BOOST_TEST(!exp); + BOOST_TEST(exp.error().reason() == "error"); + BOOST_TEST(count == 1); + } + } + + // void swap(Object&) + // friend void swap(Object&, Object&) + { + Object o1; + o1.set("hello", "world"); + Object o2; + o2.set("goodbye", "world"); + o1.swap(o2); + BOOST_TEST(o1.size() == 1); + BOOST_TEST(o2.size() == 1); + BOOST_TEST(o1.get("goodbye") == "world"); + BOOST_TEST(o2.get("hello") == "world"); + swap(o1, o2); + BOOST_TEST(o1.size() == 1); + BOOST_TEST(o2.size() == 1); + BOOST_TEST(o1.get("hello") == "world"); + BOOST_TEST(o2.get("goodbye") == "world"); + } + + // operator==(Object const&, Object const&) + // operator!=(Object const&, Object const&) + { + Object o1; + o1.set("hello", "world"); + Object o2 = o1; + BOOST_TEST(o1 == o2); + BOOST_TEST(o2 == o1); + Object o3; + o3.set("hello", "world"); + BOOST_TEST(o1 != o3); + BOOST_TEST(o3 != o2); + } + + // toString(Object const&) + { + // Behave same as JS: + // x = {hello: 'world'} + // x.toString() == '[object Object]' + Object o; + o.set("hello", "world"); + BOOST_TEST(toString(o) == "[object Object]"); + } + } + + void + function_test() + { + // Function() + { + Function f; + BOOST_TEST(f().isUndefined()); + } + + // Function(F const& f) + { + Function f([](Array const& args) + { + return args.get(0); + }); + Array a; + a.emplace_back("hello"); + BOOST_TEST(f(a) == "hello"); + } + + // Function(Function&&) + { + Function f1([](Array const& args) + { + return args.get(0); + }); + Function f2(std::move(f1)); + Array a; + a.emplace_back("hello"); + BOOST_TEST(f2(a) == "hello"); + } + + // Function(Function const&) + { + Function f1([](Array const& args) + { + return args.get(0); + }); + Function f2(f1); + Array a; + a.emplace_back("hello"); + BOOST_TEST(f1(a) == "hello"); + BOOST_TEST(f2(a) == "hello"); + } + + // operator=(Function&&) + { + Function f1([](Array const& args) + { + return args.get(0); + }); + Function f2; + f2 = std::move(f1); + Array a; + a.emplace_back("hello"); + BOOST_TEST(f2(a) == "hello"); + } + + // operator=(Function const&) + { + Function f1([](Array const& args) + { + return args.get(0); + }); + Function f2; + f2 = f1; + Array a; + a.emplace_back("hello"); + BOOST_TEST(f1(a) == "hello"); + BOOST_TEST(f2(a) == "hello"); + } + + // impl() + { + Function f([](dom::Value const& arg0) + { + return arg0; + }); + BOOST_TEST(f.impl() != nullptr); + Array a; + a.emplace_back("hello"); + BOOST_TEST(f.impl()->call(a).value() == "hello"); + } + + // type_key() + { + Function f([](dom::Value const& arg0) + { + return arg0; + }); + BOOST_TEST(std::string_view(f.type_key()) == "Function"); + } + + // Expected call(Array const& args) + { + Function f([](dom::Value const& arg0) + { + return arg0; + }); + Array a; + a.emplace_back("hello"); + BOOST_TEST(f.call(a).value() == "hello"); + } + + // operator()(Args&&... args) + { + // 0 args + { + Function f([]() + { + return "hello"; + }); + BOOST_TEST(f() == "hello"); + } + + // n args + { + Function f([](dom::Value const& arg0) + { + return arg0; + }); + BOOST_TEST(f("hello") == "hello"); + } + + // return void + { + bool called = false; + Function f([&called](dom::Value const&) + { + called = true; + return; + }); + BOOST_TEST(f("hello").isUndefined()); + BOOST_TEST(called); + } + + // return Expected + { + bool called = false; + auto fn = [&called](dom::Value const&) -> Expected + { + called = true; + return Unexpected(Error("error")); + }; + Function f(fn); + BOOST_TEST_THROWS(f("hello"), Exception); + auto exp = f.call(newArray()); + BOOST_TEST(!exp); + BOOST_TEST(exp.error().reason() == "error"); + } + + // return Value + { + Function f([](dom::Value const& arg0) + { + return arg0; + }); + BOOST_TEST(f("hello") == "hello"); + } + + // missing arguments are replaced with undefined + { + Function f([](dom::Value const& arg0) + { + return arg0; + }); + BOOST_TEST(f().isUndefined()); + } + } + + // try_invoke(Args&&... args) + // same as operator() but returns Expected instead of throwing + { + auto fn = [](dom::Value const& arg0) -> Expected + { + BOOST_TEST(arg0 == "hello"); + return Unexpected(Error("error")); + }; + Function f(fn); + auto exp = f.try_invoke("hello"); + BOOST_TEST(!exp); + BOOST_TEST(exp.error().reason() == "error"); + } + + // swap(Function& other) + // swap(Function &lhs, Function& rhs) + { + Function f1([]() + { + return "hello"; + }); + Function f2([]() + { + return "world"; + }); + f1.swap(f2); + BOOST_TEST(f1() == "world"); + BOOST_TEST(f2() == "hello"); + swap(f1, f2); + BOOST_TEST(f1() == "hello"); + BOOST_TEST(f2() == "world"); + } + + // makeVariadicInvocable(F&& f) + { + auto fn = [](Array const& args) + { + BOOST_TEST(args.size() == 2); + BOOST_TEST(args.get(0) == "hello"); + BOOST_TEST(args.get(1) == "world"); + return args.get(0); + }; + Function f = makeVariadicInvocable(fn); + BOOST_TEST(f("hello", "world") == "hello"); + } + } + + void + value_test() + { + // Value() noexcept; + { + Value v; + BOOST_TEST(v.isUndefined()); + } + + // Value(dom::Kind kind) noexcept; + { + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(v.isUndefined()); + } + + // Null + { + Value v(Kind::Null); + BOOST_TEST(v.isNull()); + } + + // Boolean + { + Value v(Kind::Boolean); + BOOST_TEST(v.isBoolean()); + BOOST_TEST(v == false); + } + + // Integer + { + Value v(Kind::Integer); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 0); + } + + // String + { + Value v(Kind::String); + BOOST_TEST(v.isString()); + BOOST_TEST(v.empty()); + } + + // SafeString + { + Value v(Kind::SafeString); + BOOST_TEST(v.isSafeString()); + BOOST_TEST(v.empty()); + } + + // Array + { + Value v(Kind::Array); + BOOST_TEST(v.isArray()); + BOOST_TEST(v.empty()); + } + + // Object + { + Value v(Kind::Object); + BOOST_TEST(v.isObject()); + BOOST_TEST(v.empty()); + } + + // Function + { + Value v(Kind::Function); + BOOST_TEST(v.isFunction()); + BOOST_TEST(v().isUndefined()); + } + } + + // Value(std::nullptr_t v) noexcept; + { + Value v(nullptr); + BOOST_TEST(v.isNull()); + } + + // Value(std::int64_t v) noexcept; + { + Value v(123); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 123); + } + + // Value(String str) noexcept; + { + Value v(String("hello")); + BOOST_TEST(v.isString()); + BOOST_TEST(v == "hello"); + } + + // Value(Array arr) noexcept; + { + Array arr; + arr.emplace_back("hello"); + Value v(arr); + BOOST_TEST(v.isArray()); + BOOST_TEST(v.size() == 1); + } + + // Value(Object obj) noexcept; + { + Object obj; + obj.set("hello", "world"); + Value v(obj); + BOOST_TEST(v.isObject()); + BOOST_TEST(v.size() == 1); + } + + // Value(Function fn) noexcept; + { + Function fn([](Array const& args) + { + return args.get(0); + }); + Value v(fn); + BOOST_TEST(v.isFunction()); + Array a; + a.emplace_back("hello"); + BOOST_TEST(v(a) == "hello"); + } + + // Value(F const& f) +{ + Value v([](dom::Value const& arg0) + { + return arg0; + }); + BOOST_TEST(v.isFunction()); + BOOST_TEST(v("hello") == "hello"); + } + + // Value(Boolean const& b) + { + Value v(true); + BOOST_TEST(v.isBoolean()); + BOOST_TEST(v == true); + } + + // Value(std::integral auto v) + { + { + Value v(123); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 123); + } + + { + Value v(0); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 0); + } + } + + // Value(std::floating_point v) + { + { + Value v(123.0); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 123); + } + + { + Value v(0.0); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 0); + } + } + + // Value(Enum v) + { + enum class E { A, B, C }; + Value v(E::A); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 0); + } + + // Value(char const(&sz)[N]) + { + Value v("hello"); + BOOST_TEST(v.isString()); + BOOST_TEST(v == "hello"); + } + + // Value(char const* s) + { + std::string s("hello"); + Value v(s.data()); + BOOST_TEST(v.isString()); + BOOST_TEST(v == "hello"); + } + + // Value(StringLike) + { + Value v(std::string("hello")); + BOOST_TEST(v.isString()); + BOOST_TEST(v == "hello"); + } + + // Value(std::optional const& opt) + { + { + std::optional opt; + Value v(opt); + BOOST_TEST(v.isUndefined()); + } + + { + std::optional opt(123); + Value v(opt); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 123); + } + } + + // Value(Optional) + { + { + Optional opt; + Value v(opt); + BOOST_TEST(v.isUndefined()); + } + + { + Optional opt(123); + Value v(opt); + BOOST_TEST(v.isInteger()); + BOOST_TEST(v == 123); + } + } + + // Value(Array::storage_type) + { + Array::storage_type v; + v.emplace_back("hello"); + Value val(v); + BOOST_TEST(val.isArray()); + BOOST_TEST(val.size() == 1); + } + + // Value(Value const& other); + { + Value v1(123); + Value v2(v1); + BOOST_TEST(v2.isInteger()); + BOOST_TEST(v2 == 123); + BOOST_TEST(v1.isInteger()); + BOOST_TEST(v1 == 123); + } + + // Value(Value&& other) noexcept; + { + Value v1(123); + Value v2(std::move(v1)); + BOOST_TEST(v2.isInteger()); + BOOST_TEST(v2 == 123); + } + + // Value& operator=(Value&& other) + { + Value v1(123); + Value v2; + v2 = std::move(v1); + BOOST_TEST(v2.isInteger()); + BOOST_TEST(v2 == 123); + } + + // Value& operator=(Value const&) + { + Value v1(123); + Value v2; + v2 = v1; + BOOST_TEST(v2.isInteger()); + BOOST_TEST(v2 == 123); + BOOST_TEST(v1.isInteger()); + BOOST_TEST(v1 == 123); + } + + // char const* type_key() + { + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(std::string_view(v.type_key()) == "undefined"); + } + + // Null + { + Value v(Kind::Null); + BOOST_TEST(std::string_view(v.type_key()) == "null"); + } + + // Boolean + { + Value v(Kind::Boolean); + BOOST_TEST(std::string_view(v.type_key()) == "boolean"); + } + + // Integer + { + Value v(Kind::Integer); + BOOST_TEST(std::string_view(v.type_key()) == "integer"); + } + + // String + { + Value v(Kind::String); + BOOST_TEST(std::string_view(v.type_key()) == "string"); + } + + // SafeString + { + Value v(Kind::SafeString); + BOOST_TEST(std::string_view(v.type_key()) == "safeString"); + } + + // Array + { + Value v(Kind::Array); + BOOST_TEST(std::string_view(v.type_key()) == "Array"); + } + + // Object + { + Value v(Kind::Object); + BOOST_TEST(std::string_view(v.type_key()) == "Object"); + } + + // Function + { + Value v(Kind::Function); + BOOST_TEST(std::string_view(v.type_key()) == "Function"); + } + } + + // dom::Kind kind() + // bool isUndefined() + // bool isNull() + // bool isBoolean() + // bool isInteger() + // bool isString() + // bool isSafeString() + // bool isArray() + // bool isObject() + // bool isFunction() + { + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(v.kind() == Kind::Undefined); + BOOST_TEST(v.isUndefined()); + } + + // Null + { + Value v(Kind::Null); + BOOST_TEST(v.kind() == Kind::Null); + BOOST_TEST(v.isNull()); + } + + // Boolean + { + Value v(Kind::Boolean); + BOOST_TEST(v.kind() == Kind::Boolean); + BOOST_TEST(v.isBoolean()); + } + + // Integer + { + Value v(Kind::Integer); + BOOST_TEST(v.kind() == Kind::Integer); + BOOST_TEST(v.isInteger()); + } + + // String + { + Value v(Kind::String); + BOOST_TEST(v.kind() == Kind::String); + BOOST_TEST(v.isString()); + } + + // SafeString + { + Value v(Kind::SafeString); + BOOST_TEST(v.kind() == Kind::SafeString); + BOOST_TEST(v.isSafeString()); + } + + // Array + { + Value v(Kind::Array); + BOOST_TEST(v.kind() == Kind::Array); + BOOST_TEST(v.isArray()); + } + + // Object + { + Value v(Kind::Object); + BOOST_TEST(v.kind() == Kind::Object); + BOOST_TEST(v.isObject()); + } + + // Function + { + Value v(Kind::Function); + BOOST_TEST(v.kind() == Kind::Function); + BOOST_TEST(v.isFunction()); + } + } + + // bool isTruthy() + // operator bool() + { + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(! v.isTruthy()); + BOOST_TEST(! v); + } + + // Null + { + Value v(Kind::Null); + BOOST_TEST(! v.isTruthy()); + BOOST_TEST(! v); + } + + // Boolean + { + Value v(Kind::Boolean); + BOOST_TEST(! v.isTruthy()); + BOOST_TEST(! v); + v = true; + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + } + + // Integer + { + Value v(Kind::Integer); + BOOST_TEST(! v.isTruthy()); + BOOST_TEST(! v); + v = 123; + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + } + + // String + { + Value v(Kind::String); + BOOST_TEST(! v.isTruthy()); + BOOST_TEST(! v); + v = "hello"; + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + } + + // SafeString + { + Value v(Kind::SafeString); + BOOST_TEST(! v.isTruthy()); + BOOST_TEST(! v); + v = "hello"; + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + } + + // Array + { + Value v(Kind::Array); + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + v = Array(); + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + } + + // Object + { + Value v(Kind::Object); + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + v = Object(); + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + } + + // Function + { + Value v(Kind::Function); + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + v = Function(); + BOOST_TEST(v.isTruthy()); + BOOST_TEST(v); + } + } + + // getBool() + { + Value v(true); + BOOST_TEST(v.getBool()); + v = false; + BOOST_TEST(! v.getBool()); + } + + // getInteger() + { + Value v(123); + BOOST_TEST(v.getInteger() == 123); + } + + // getString() + { + Value v("hello"); + BOOST_TEST(v.getString() == "hello"); + } + + // getArray() + { + Array arr; + arr.emplace_back("hello"); + Value v(arr); + BOOST_TEST(v.getArray().size() == 1); + } + + // getObject() + { + Object obj; + obj.set("hello", "world"); + Value v(obj); + BOOST_TEST(v.getObject().size() == 1); + } + + // getFunction() + { + Function fn([](Array const& args) + { + return args.get(0); + }); + Value v(fn); + Array a; + a.emplace_back("hello"); + BOOST_TEST(v.getFunction()(a) == "hello"); + } + + // get(std::string_view key) + { + // Object + { + Object obj; + obj.set("hello", "world"); + Value v(obj); + BOOST_TEST(v.get("hello") == "world"); + } + + // Array + { + Array arr; + arr.emplace_back("hello"); + Value v(arr); + BOOST_TEST(v.get("0") == "hello"); + BOOST_TEST(v.get("10").isUndefined()); + BOOST_TEST(v.get("hello").isUndefined()); + } + + // String + { + Value v("hello"); + BOOST_TEST(v.get("0") == "h"); + BOOST_TEST(v.get("10").isUndefined()); + BOOST_TEST(v.get("hello").isUndefined()); + } + + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(v.get("hello").isUndefined()); + } + } + + // get(std::size_t i) + { + // Object + { + Object obj; + obj.set("hello", "world"); + obj.set("1", "goodbye"); + Value v(obj); + BOOST_TEST(v.get(0).isUndefined()); + BOOST_TEST(v.get(1) == "goodbye"); + } + + // Array + { + Array arr; + arr.emplace_back("hello"); + Value v(arr); + BOOST_TEST(v.get(0) == "hello"); + BOOST_TEST(v.get(1).isUndefined()); + } + + // String + { + Value v("hello"); + BOOST_TEST(v.get(0) == "h"); + BOOST_TEST(v.get(5).isUndefined()); + } + + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(v.get(0).isUndefined()); + } + } + + // lookup + { + Object d; + d.set("d", "e"); + Object obj; + obj.set("a", "b"); + obj.set("c", d); + Array arr; + arr.emplace_back("hello"); + obj.set("arr", arr); + Value v(obj); + BOOST_TEST(v.lookup("a") == "b"); + BOOST_TEST(v.lookup("c").isObject()); + BOOST_TEST(v.lookup("c.d") == "e"); + BOOST_TEST(v.lookup("c.f").isUndefined()); + BOOST_TEST(v.lookup("arr.0") == "hello"); + BOOST_TEST(v.lookup("arr.1").isUndefined()); + } + + // set(String const& key, Value const& value) + { + // Object + { + Value v(Kind::Object); + v.set("hello", "world"); + BOOST_TEST(v.get("hello") == "world"); + } + + // Array + { + Value v(Kind::Array); + v.set("0", "hello"); + BOOST_TEST(v.get("0") == "hello"); + } + } + + // exists(std::string_view key) + { + // Object + { + Object obj; + obj.set("hello", "world"); + Value v(obj); + BOOST_TEST(v.exists("hello")); + BOOST_TEST(! v.exists("goodbye")); + } + + // Array + { + Array arr; + arr.emplace_back("hello"); + Value v(arr); + BOOST_TEST(v.exists("0")); + BOOST_TEST(! v.exists("1")); + } + + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(! v.exists("hello")); + } + } + + // Value operator() + { + Value v(Kind::Function); + BOOST_TEST(v().isUndefined()); + } + + // size() + // empty() + { + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(v.size() == 0); // NOLINT(*-container-size-empty) + BOOST_TEST(v.empty()); + } + + // Null + { + Value v(Kind::Null); + BOOST_TEST(v.size() == 0); // NOLINT(*-container-size-empty) + BOOST_TEST(v.empty()); + } + + // Boolean + { + Value v(Kind::Boolean); + BOOST_TEST(v.size() == 1); + BOOST_TEST(!v.empty()); + } + + // Integer + { + Value v(Kind::Integer); + BOOST_TEST(v.size() == 1); + BOOST_TEST(!v.empty()); + } + + // String + { + Value v(Kind::String); + BOOST_TEST(v.size() == 0); // NOLINT(*-container-size-empty) + BOOST_TEST(v.empty()); + v = "hello"; + BOOST_TEST(v.size() == 5); + BOOST_TEST(!v.empty()); + } + + // SafeString + { + Value v(Kind::SafeString); + BOOST_TEST(v.size() == 0); // NOLINT(*-container-size-empty) + BOOST_TEST(v.empty()); + v = "hello"; + BOOST_TEST(v.size() == 5); + BOOST_TEST(!v.empty()); + } + + // Array + { + Value v(Kind::Array); + BOOST_TEST(v.size() == 0); // NOLINT(*-container-size-empty) + BOOST_TEST(v.empty()); + v.getArray().push_back("hello"); + BOOST_TEST(v.size() == 1); + BOOST_TEST(!v.empty()); + } + + // Object + { + Value v(Kind::Object); + BOOST_TEST(v.size() == 0); // NOLINT(*-container-size-empty) + BOOST_TEST(v.empty()); + v.set("hello", "world"); + BOOST_TEST(v.size() == 1); + BOOST_TEST(!v.empty()); + } + + // Function + { + Value v(Kind::Function); + BOOST_TEST(v.size() == 1); + BOOST_TEST(!v.empty()); + } + } + + // toString + { + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(toString(v) == "undefined"); + } + + // Null + { + Value v(Kind::Null); + BOOST_TEST(toString(v) == "null"); + } + + // Boolean + { + Value v(Kind::Boolean); + BOOST_TEST(toString(v) == "false"); + v = true; + BOOST_TEST(toString(v) == "true"); + } + + // Integer + { + Value v(Kind::Integer); + BOOST_TEST(toString(v) == "0"); + v = 123; + BOOST_TEST(toString(v) == "123"); + } + + // String + { + Value v(Kind::String); + BOOST_TEST(toString(v).empty()); + v = "hello"; + BOOST_TEST(toString(v) == "hello"); + } + + // SafeString + { + Value v(Kind::SafeString); + BOOST_TEST(toString(v).empty()); + v = "hello"; + BOOST_TEST(toString(v) == "hello"); + } + + // Array + { + Value v(Kind::Array); + BOOST_TEST(toString(v).empty()); + v.getArray().push_back("hello"); + BOOST_TEST(toString(v) == "hello"); + v.getArray().push_back("world"); + BOOST_TEST(toString(v) == "hello,world"); + } + + // Object + { + Value v(Kind::Object); + BOOST_TEST(toString(v) == "[object Object]"); + v.getObject().set("hello", "world"); + BOOST_TEST(toString(v) == "[object Object]"); + } + + // Function + { + Value v(Kind::Function); + BOOST_TEST(toString(v) == "[object Function]"); + } + } + + // swap(Value& other) + // swap(Value& lhs, Value& rhs) + { + Value v1(123); + Value v2("hello"); + v1.swap(v2); + BOOST_TEST(v1.isString()); + BOOST_TEST(v1 == "hello"); + BOOST_TEST(v2.isInteger()); + BOOST_TEST(v2 == 123); + swap(v1, v2); + BOOST_TEST(v1.isInteger()); + BOOST_TEST(v1 == 123); + BOOST_TEST(v2.isString()); + BOOST_TEST(v2 == "hello"); + } + + // operator==(Value const&, Value const&) + { + // Types are not the same + { + Value v1(123); + Value v2("hello"); + BOOST_TEST(v1 != v2); + BOOST_TEST(v2 != v1); + } + + // Undefined + { + Value v1(Kind::Undefined); + Value v2(Kind::Undefined); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // Null + { + Value v1(Kind::Null); + Value v2(Kind::Null); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // Boolean + { + Value v1(Kind::Boolean); + Value v2(Kind::Boolean); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + v1 = true; + BOOST_TEST(v1 != v2); + BOOST_TEST(v2 != v1); + v2 = true; + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // Integer + { + Value v1(Kind::Integer); + Value v2(Kind::Integer); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + v1 = 123; + BOOST_TEST(v1 != v2); + BOOST_TEST(v2 != v1); + v2 = 123; + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // String + { + Value v1(Kind::String); + Value v2(Kind::String); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + v1 = "hello"; + BOOST_TEST(v1 != v2); + BOOST_TEST(v2 != v1); + v2 = "hello"; + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // SafeString + { + Value v1(Kind::SafeString); + Value v2(Kind::SafeString); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + v1 = "hello"; + BOOST_TEST(v1 != v2); + BOOST_TEST(v2 != v1); + v2 = "hello"; + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // Array + { + Value v1(Kind::Array); + Value v2(Kind::Array); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + v1 = v2; + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // Object + { + Value v1(Kind::Object); + Value v2(Kind::Object); + BOOST_TEST(v1 != v2); + BOOST_TEST(v2 != v1); + v1 = v2; + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + + // Function + { + Value v1(Kind::Function); + Value v2(Kind::Function); + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + v2 = Value([](){}); + BOOST_TEST(v1 != v2); + BOOST_TEST(v2 != v1); + v1 = v2; + BOOST_TEST(v1 == v2); + BOOST_TEST(v2 == v1); + } + } + + // operator<=> + { + // Types are not the same + { + Value v1(123); + Value v2("hello"); + BOOST_TEST(v1 < v2); + BOOST_TEST(v2 > v1); + } + + // Undefined + { + Value v1(Kind::Undefined); + Value v2(Kind::Undefined); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equivalent); + } + + // Null + { + Value v1(Kind::Null); + Value v2(Kind::Null); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equivalent); + } + + // Boolean + { + Value v1(Kind::Boolean); + Value v2(Kind::Boolean); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + v1 = true; + BOOST_TEST(v1 > v2); + v2 = true; + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + } + + // Integer + { + Value v1(Kind::Integer); + Value v2(Kind::Integer); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + v1 = 123; + BOOST_TEST(v1 > v2); + v2 = 123; + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + } + + // String + { + Value v1(Kind::String); + Value v2(Kind::String); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + v1 = "hello"; + BOOST_TEST(v1 > v2); + v2 = "hello"; + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + } + + // Array + { + Value v1(Kind::Array); + Value v2(Kind::Array); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + v1 = v2; + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + Value v3(Kind::Array); + v3.getArray().push_back("hello"); + BOOST_TEST(v1 < v3); + } + + // Object + { + Value v1(Kind::Object); + Value v2(Kind::Object); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equivalent); + v1 = v2; + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + Value v3(Kind::Object); + v3.getObject().set("hello", "world"); + BOOST_TEST(v1 <=> v3 == std::strong_ordering::equivalent); + } + + // Function + { + Value v1(Kind::Function); + Value v2(Kind::Function); + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equivalent); + v1 = v2; + BOOST_TEST(v1 <=> v2 == std::strong_ordering::equal); + } + } + + // operator+ + { + // Same types + { + // Integer + { + Value v1(123); + Value v2(456); + BOOST_TEST((v1 + v2).isInteger()); + BOOST_TEST((v1 + v2) == 579); + } + + // String + { + Value v1("hello"); + Value v2("world"); + BOOST_TEST((v1 + v2).isString()); + BOOST_TEST((v1 + v2) == "helloworld"); + } + + // Array + { + Value v1(Kind::Array); + v1.getArray().push_back("hello"); + Value v2(Kind::Array); + v2.getArray().push_back("world"); + BOOST_TEST((v1 + v2).isArray()); + BOOST_TEST((v1 + v2).getArray().size() == 2); + BOOST_TEST((v1 + v2).getArray().get(0) == "hello"); + BOOST_TEST((v1 + v2).getArray().get(1) == "world"); + } + } + + // Arithmetic types (number + boolean) + { + Value v1(123); + Value v2(true); + BOOST_TEST((v1 + v2).isInteger()); + BOOST_TEST((v1 + v2) == 124); + BOOST_TEST((v2 + v1).isInteger()); + BOOST_TEST((v2 + v1) == 124); + } + + // coerce to strings + { + Value v1(123); + Value v2("hello"); + BOOST_TEST((v1 + v2).isString()); + BOOST_TEST((v1 + v2) == "123hello"); + BOOST_TEST((v2 + v1).isString()); + BOOST_TEST((v2 + v1) == "hello123"); + } + } + + // operator|| returns the first truthy value + { + Value v1(Kind::Undefined); + Value v2(Kind::Undefined); + BOOST_TEST((v1 || v2).isUndefined()); + v1 = 123; + BOOST_TEST((v1 || v2).isInteger()); + BOOST_TEST((v1 || v2) == 123); + v2 = 456; + BOOST_TEST((v1 || v2).isInteger()); + BOOST_TEST((v1 || v2) == 123); + v1 = 0; + BOOST_TEST((v1 || v2).isInteger()); + BOOST_TEST((v1 || v2) == 456); + } + + // operator&& returns the first falsy value + { + Value v1(Kind::Undefined); + Value v2(Kind::Undefined); + BOOST_TEST((v1 && v2).isUndefined()); + v1 = 123; + BOOST_TEST((v1 && v2).isUndefined()); + v2 = 456; + BOOST_TEST((v1 && v2).isInteger()); + BOOST_TEST((v1 && v2) == 456); + v1 = 0; + BOOST_TEST((v1 && v2).isInteger()); + BOOST_TEST((v1 && v2) == 0); + } + + // JSON::stringify(dom::Value) + { + // Undefined + { + Value v(Kind::Undefined); + BOOST_TEST(JSON::stringify(v) == "null"); + } + + // Null + { + Value v(Kind::Null); + BOOST_TEST(JSON::stringify(v) == "null"); + } + + // Boolean + { + Value v(Kind::Boolean); + BOOST_TEST(JSON::stringify(v) == "false"); + v = true; + BOOST_TEST(JSON::stringify(v) == "true"); + } + + // Integer + { + Value v(Kind::Integer); + BOOST_TEST(JSON::stringify(v) == "0"); + v = 123; + BOOST_TEST(JSON::stringify(v) == "123"); + } + + // String + { + Value v(Kind::String); + BOOST_TEST(JSON::stringify(v) == "\"\""); + v = "hello"; + BOOST_TEST(JSON::stringify(v) == "\"hello\""); + } + + // Array + { + Array arr; + arr.emplace_back("hello"); + Value v(arr); + BOOST_TEST(JSON::stringify(v) == "[\n \"hello\"\n]"); + } + + // Object + { + Object obj; + obj.set("hello", "world"); + obj.set("goodbye", "world"); + Value v(obj); + BOOST_TEST(JSON::stringify(v) == "{\n \"hello\": \"world\",\n \"goodbye\": \"world\"\n}"); + } + } + } + void run() { + kind_test(); + string_test(); + array_test(); + object_test(); + function_test(); + value_test(); } }; diff --git a/src/test/lib/Support/Handlebars.cpp b/src/test/lib/Support/Handlebars.cpp index 39ff2adf1..2f32b8bda 100644 --- a/src/test/lib/Support/Handlebars.cpp +++ b/src/test/lib/Support/Handlebars.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace clang { namespace mrdox { @@ -307,28 +308,28 @@ setup_helpers() "progress helper requires 3 arguments: {} provided", arguments.size()); } - if (!arguments[0].isString()) + if (!arguments.get(0).isString()) { return fmt::format( "progress helper requires string argument: {} received", - arguments[0]); + arguments.get(0)); } - if (!arguments[1].isInteger()) + if (!arguments.get(1).isInteger()) { return fmt::format( "progress helper requires number argument: {} received", - arguments[1]); + arguments.get(1)); } - if (!arguments[2].isBoolean()) + if (!arguments.get(2).isBoolean()) { return fmt::format( "progress helper requires boolean argument: {} received", - arguments[2]); + arguments.get(2)); } - dom::Value nameV = arguments[0]; + dom::Value nameV = arguments.get(0); std::string_view name = nameV.getString(); - std::uint64_t percent = arguments[1].getInteger(); - bool stalled = arguments[2].getBool(); + std::uint64_t percent = arguments.get(1).getInteger(); + bool stalled = arguments.get(2).getBool(); std::uint64_t barWidth = percent / 5; std::string bar = std::string(20, '*').substr(0, barWidth); std::string stalledStr = stalled ? "stalled" : ""; @@ -346,16 +347,16 @@ setup_helpers() [](dom::Array const& arguments) -> dom::Value { dom::Value options = arguments.back(); - if (options["fn"]) + if (options.get("fn")) { // If the hook is not overridden, then the default implementation will // mimic the behavior of Mustache and just render the block. - options["write"](options["context"]); + options.get("write")(options.get("context")); return {}; } if (arguments.size() > 1) { - return fmt::format(R"(Missing helper: "{}")", options["name"]); + return fmt::format(R"(Missing helper: "{}")", options.get("name")); } return {}; }); @@ -372,7 +373,7 @@ setup_helpers() std::size_t const n = args.size(); for (std::size_t i = 0; i < n - 1; ++i) { - if (!args[i].isString()) + if (!args.get(i).isString()) { return fmt::format("link helper requires string arguments: {} provided", args.size()); } @@ -380,19 +381,19 @@ setup_helpers() std::string out; dom::Value options = args.back(); - dom::Value hash = options["hash"]; - auto h = hash["href"]; + dom::Value hash = options.get("hash"); + auto h = hash.get("href"); if (h.isString()) { out += h.getString(); } else if (args.size() > 1) { - if (!args[1].isString()) + if (!args.get(1).isString()) { - return fmt::format("link helper requires string argument: {} provided", toString(args[1].kind())); + return fmt::format("link helper requires string argument: {} provided", toString(args.get(1).kind())); } - auto href = args[1]; + auto href = args.get(1); out += href.getString(); } else @@ -401,23 +402,24 @@ setup_helpers() } out += '['; - out += args[0].getString(); + out += args.get(0).getString(); // more attributes from hashes if (hash) { - dom::Object hashObj = hash.getObject(); - for (auto const& [key, value] : hashObj) + dom::Object const& hashObj = hash.getObject(); + hashObj.visit([&](dom::String const& key, dom::Value const& value) { if (key == "href" || !value.isString()) { - continue; + return true; } out += ','; out += key; out += '='; out += value.getString(); - } + return true; + }); } out += ']'; @@ -429,7 +431,7 @@ setup_helpers() { std::string res; dom::Value options = args.back(); - dom::Value fn = options["fn"]; + dom::Value fn = options.get("fn"); if (fn.isFunction()) { res = static_cast(fn()); @@ -440,7 +442,7 @@ setup_helpers() { return "loud helper requires at least one argument"; } - dom::Array::value_type const& firstArg = args[0]; + dom::Array::value_type const& firstArg = args.get(0); if (!firstArg.isString()) { return fmt::format("loud helper requires string argument: {} provided", toString(firstArg.kind())); @@ -463,7 +465,7 @@ setup_helpers() master.hbs.registerHelper("bold", dom::makeVariadicInvocable([]( dom::Array const& args) { dom::Value options = args.back(); - return fmt::format(R"(
{}
)", options["fn"]()); + return fmt::format(R"(
{}
)", options.get("fn")()); })); master.hbs.registerHelper("list", dom::makeVariadicInvocable([]( @@ -473,42 +475,42 @@ setup_helpers() { return fmt::format("list helper requires 1 argument: {} provided", args.size() - 1); } - if (!args[0].isArray()) + if (!args.get(0).isArray()) { - return fmt::format("list helper requires array argument: {} provided", toString(args[0].kind())); + return fmt::format("list helper requires array argument: {} provided", toString(args.get(0).kind())); } dom::Value options = args.back(); - dom::Object data = createFrame(options["data"]); - dom::Value itemsV = args[0]; + dom::Object data = createFrame(options.get("data")); + dom::Value itemsV = args.get(0); dom::Array const& items = itemsV.getArray(); if (!items.empty()) { std::string out = "(i)); data.set("first", i == 0); data.set("last", i == items.size() - 1); data.set("index", static_cast(i)); dom::Object fn_options; fn_options.set("data", data); - out += toString("
  • " + options["fn"](item, fn_options) + "
  • "); + out += toString("
  • " + options.get("fn")(item, fn_options) + "
  • "); } return out + ""; } - return options["inverse"](); + return options.get("inverse")(); })); master.hbs.registerHelper("isdefined", @@ -524,7 +526,7 @@ setup_helpers() dom::Value options = args.back(); std::string out; out += "Missing: "; - out += toString(options["name"]); + out += toString(options.get("name")); out += "("; std::size_t const n = args.size(); for (std::size_t i = 0; i < n - 1; ++i) { @@ -532,7 +534,7 @@ setup_helpers() { out += ", "; } - out += toString(args[i]); + out += toString(args.get(i)); } out += ")"; return out; @@ -546,9 +548,9 @@ setup_helpers() OutputRef os(out); os << "Helper '"; dom::Value options = args.back(); - os << options["name"]; + os << options.get("name"); os << "' not found. Printing block: "; - os << options["fn"](); + os << options.get("fn")(); return out; })); } @@ -560,7 +562,7 @@ setup_logger() dom::makeVariadicInvocable([this]( dom::Array const& args) { - dom::Value level = args[0]; + dom::Value level = args.get(0); master.log += fmt::format("[{}] ", level); for (std::size_t i = 1; i < args.size(); ++i) { @@ -568,7 +570,7 @@ setup_logger() { master.log += ", "; } - master.log += args[i].getString(); + master.log += args.get(i).getString(); } master.log += '\n'; })); @@ -862,7 +864,7 @@ basic_context() ctx = {}; ctx.set("awesome", [](dom::Value const& options){ - return options["context"]["more"]; + return options.lookup("context.more"); }); ctx.set("more", "More awesome"); BOOST_TEST(hbs.render("{{awesome}}", ctx) == "More awesome"); @@ -907,7 +909,7 @@ basic_context() dom::Object ctx; ctx.set("awesome", []( dom::Value const& context, dom::Value const& options) { - return options["fn"](context); + return options.get("fn")(context); }); BOOST_TEST(hbs.render("{{#awesome 1}}inner {{.}}{{/awesome}}", ctx) == "inner 1"); } @@ -918,7 +920,7 @@ basic_context() ctx.set("value", true); ctx.set("awesome", []( dom::Value const& context, dom::Value const& options) { - return options["fn"](context); + return options.get("fn")(context); }); BOOST_TEST(hbs.render("{{#with value}}{{#../awesome 1}}inner {{.}}{{/../awesome}}{{/with}}", ctx) == "inner 1"); } @@ -928,7 +930,7 @@ basic_context() // block functions are called with options dom::Object ctx; ctx.set("awesome", [](dom::Value const& options) { - return options["fn"](options["context"]); + return options.get("fn")(options.get("context")); }); BOOST_TEST(hbs.render("{{#awesome}}inner{{/awesome}}", ctx) == "inner"); } @@ -939,7 +941,7 @@ basic_context() dom::Object ctx; dom::Object foo; foo.set("awesome", [](dom::Value const& options) { - return options["context"]; + return options.get("context"); }); ctx.set("foo", foo); BOOST_TEST(hbs.render("{{#foo.awesome}}inner{{/foo.awesome}}", ctx) == "inner"); @@ -951,7 +953,7 @@ basic_context() ctx.set("value", true); ctx.set("awesome", [](dom::Value const& options) { - return options["context"]; + return options.get("context"); }); BOOST_TEST(hbs.render("{{#with value}}{{#../awesome}}inner{{/../awesome}}{{/with}}", ctx) == "inner"); } @@ -1015,7 +1017,7 @@ basic_context() // literal references only convert to strings as helper parameters // literal references as main helper names will decay to context keys - BOOST_TEST(hbs.render("{{\"\\n\"}}") == ""); + BOOST_TEST(hbs.render("{{\"\\n\"}}").empty()); } // that current context path ({{.}}) doesn't hit helpers @@ -2398,7 +2400,7 @@ subexpressions() // as hashes { hbs.registerHelper("blog", [](dom::Value const& options) { - return "val is " + options["hash"]["fun"]; + return "val is " + options.lookup("hash.fun"); }); hbs.registerHelper("equal", []( dom::Value const& x, dom::Value const& y) { @@ -2412,9 +2414,9 @@ subexpressions() // multiple subexpressions in a hash { hbs.registerHelper("input", [](dom::Value const& options) { - dom::Value hash = options["hash"]; - std::string ariaLabel = escapeExpression(hash["aria-label"]); - std::string placeholder = escapeExpression(hash["placeholder"]); + dom::Value hash = options.get("hash"); + std::string ariaLabel = escapeExpression(hash.get("aria-label")); + std::string placeholder = escapeExpression(hash.get("placeholder")); std::string res = "" + options["context"]["text"] + ""; + return "" + options.lookup("context.text") + ""; }); BOOST_TEST(hbs.render(string, ctx) == "Goodbye"); } @@ -3560,7 +3562,7 @@ helpers() dom::Object ctx; ctx.set("test", "hello"); hbs.registerHelper("raw", [](dom::Value const& options) { - return options["fn"](); + return options.get("fn")(); }); BOOST_TEST(hbs.render(string, ctx) == " {{test}} "); } @@ -3576,7 +3578,7 @@ helpers() dom::Value const& c, dom::Value const& options) { - return options["fn"]() + a + b + c; + return options.get("fn")() + a + b + c; }); BOOST_TEST(hbs.render(string, ctx) == " {{test}} 123"); } @@ -3584,7 +3586,7 @@ helpers() // raw block parsing (with identity helper-function) { hbs.registerHelper("identity", [](dom::Value const& options) { - return options["fn"](); + return options.get("fn")(); }); // helper for nested raw block gets raw content @@ -3617,9 +3619,9 @@ helpers() ctx.set("name", "Alan"); hbs.registerHelper("goodbyes", [](dom::Value const& options) { std::string out; - out += toString("Goodbye " + options["fn"](options["context"]) + "! "); - out += toString("goodbye " + options["fn"](options["context"]) + "! "); - out += toString("GOODBYE " + options["fn"](options["context"]) + "! "); + out += toString("Goodbye " + options.get("fn")(options.get("context")) + "! "); + out += toString("goodbye " + options.get("fn")(options.get("context")) + "! "); + out += toString("GOODBYE " + options.get("fn")(options.get("context")) + "! "); return out; }); BOOST_TEST(hbs.render(string, ctx) == "Goodbye Alan! goodbye Alan! GOODBYE Alan! "); @@ -3633,9 +3635,9 @@ helpers() hbs.registerHelper("goodbyes", [](dom::Value const& options) { std::string out; dom::Object newContext = {}; - out += toString("Goodbye " + options["fn"](newContext) + "! "); - out += toString("goodbye " + options["fn"](newContext) + "! "); - out += toString("GOODBYE " + options["fn"](newContext) + "! "); + out += toString("Goodbye " + options.get("fn")(newContext) + "! "); + out += toString("goodbye " + options.get("fn")(newContext) + "! "); + out += toString("GOODBYE " + options.get("fn")(newContext) + "! "); return out; }); BOOST_TEST(hbs.render(string, ctx) == "Goodbye Alan! goodbye Alan! GOODBYE Alan! "); @@ -3654,7 +3656,7 @@ helpers() ctx.set("goodbyes", goodbyes); ctx.set("prefix", "/root"); hbs.registerHelper("link", [](dom::Value const& prefix, dom::Value const& options) { - return "" + options["fn"](options["context"]) + ""; + return "" + options.get("fn")(options.get("context")) + ""; }); BOOST_TEST(hbs.render(string, ctx) == "Goodbye"); } @@ -3677,7 +3679,7 @@ helpers() hbs.registerHelper("goodbyes", [](dom::Value const& options) { dom::Object ctx; ctx.set("text", "GOODBYE"); - return options["fn"](ctx); + return options.get("fn")(ctx); }); BOOST_TEST(hbs.render(string, ctx) == "GOODBYE! cruel world!"); } @@ -3688,7 +3690,7 @@ helpers() dom::Object ctx; ctx.set("name", "Yehuda"); hbs.registerHelper("form", [](dom::Value const& options) { - return "
    " + options["fn"](options["context"]) + "
    "; + return "
    " + options.get("fn")(options.get("context")) + "
    "; }); BOOST_TEST(hbs.render(string, ctx) == "

    Yehuda

    "); } @@ -3709,8 +3711,8 @@ helpers() ctx.set("people", people); hbs.registerHelper("link", [](dom::Value const& options) { std::string out; - out += toString(""); - out += toString(options["fn"](options["context"])); + out += toString(""); + out += toString(options.get("fn")(options.get("context"))); out += ""; return out; }); @@ -3731,7 +3733,7 @@ helpers() yehuda.set("name", "Yehuda"); ctx.set("yehuda", yehuda); hbs.registerHelper("form", [](dom::Value const& context, dom::Value const& options) { - return "
    " + options["fn"](context) + "
    "; + return "
    " + options.get("fn")(context) + "
    "; }); BOOST_TEST(hbs.render(string, ctx) == "

    Yehuda

    "); } @@ -3747,7 +3749,7 @@ helpers() yehuda.set("cat", cat); ctx.set("yehuda", yehuda); hbs.registerHelper("form", [](dom::Value const& context, dom::Value const& options) { - return "
    " + options["fn"](context) + "
    "; + return "
    " + options.get("fn")(context) + "
    "; }); BOOST_TEST(hbs.render(string, ctx) == "

    Harold

    "); } @@ -3762,14 +3764,14 @@ helpers() hbs.registerHelper("link", [](dom::Value const& options) { std::string out; out += ""; - out += toString(options["fn"](options["context"])); + out += toString(options.get("fn")(options.get("context"))); out += ""; return out; }); hbs.registerHelper("form", [](dom::Value const& context, dom::Value const& options) { - return "
    " + options["fn"](context) + "
    "; + return "
    " + options.get("fn")(context) + "
    "; }); BOOST_TEST(hbs.render(string, ctx) == "

    Yehuda

    Hello
    "); } @@ -3784,13 +3786,13 @@ helpers() for (auto const& person: context.getArray()) { out += "
  • "; - out += toString(options["fn"](person)); + out += toString(options.get("fn")(person)); out += "
  • "; } out += ""; return out; } - return "

    " + options["inverse"](options["context"]) + "

    "; + return "

    " + options.get("inverse")(options.get("context")) + "

    "; }); // an inverse wrapper is passed in as a new context @@ -4050,7 +4052,7 @@ helpers() ctx.set("greeting", "Goodbye"); ctx.set("adj", cruel); ctx.set("noun", world); - return options["fn"](ctx); + return options.get("fn")(ctx); }); dom::Object ctx; ctx.set("cruel", "cruel"); @@ -4067,9 +4069,9 @@ helpers() hbs.registerHelper("goodbye", [](dom::Value const& options) { return "GOODBYE " + - options["hash"]["cruel"] + " " + - options["hash"]["world"] + " " + - options["hash"]["times"] + " TIMES"; + options.lookup("hash.cruel") + " " + + options.lookup("hash.world") + " " + + options.lookup("hash.times") + " TIMES"; }); BOOST_TEST(hbs.render(string) == "GOODBYE CRUEL WORLD 12 TIMES"); } @@ -4077,16 +4079,16 @@ helpers() // helpers can take an optional hash with booleans { hbs.registerHelper("goodbye", [](dom::Value const& options) -> std::string { - if (options["hash"]["print"] == true) + if (options.lookup("hash.print") == true) { std::string out; out += "GOODBYE "; - out += toString(options["hash"]["cruel"]); + out += toString(options.lookup("hash.cruel")); out += " "; - out += toString(options["hash"]["world"]); + out += toString(options.lookup("hash.world")); return out; } - else if (options["hash"]["print"] == false) + else if (options.lookup("hash.print") == false) { return "NOT PRINTING"; } @@ -4106,11 +4108,11 @@ helpers() hbs.registerHelper("goodbye", [](dom::Value const& options) { std::string out; out += "GOODBYE "; - out += toString(options["hash"]["cruel"]); + out += toString(options.lookup("hash.cruel")); out += " "; - out += toString(options["fn"](options["context"])); + out += toString(options.get("fn")(options.get("context"))); out += " "; - out += toString(options["hash"]["times"]); + out += toString(options.lookup("hash.times")); out += " TIMES"; return out; }); @@ -4124,11 +4126,11 @@ helpers() hbs.registerHelper("goodbye", [](dom::Value const& options) { std::string out; out += "GOODBYE "; - out += toString(options["hash"]["cruel"]); + out += toString(options.lookup("hash.cruel")); out += " "; - out += toString(options["fn"](options["context"])); + out += toString(options.get("fn")(options.get("context"))); out += " "; - out += toString(options["hash"]["times"]); + out += toString(options.lookup("hash.times")); out += " TIMES"; return out; }); @@ -4138,16 +4140,16 @@ helpers() // block helpers can take an optional hash with booleans { hbs.registerHelper("goodbye", [](dom::Value const& options) -> std::string { - if (options["hash"]["print"] == true) + if (options.lookup("hash.print") == true) { std::string out; out += "GOODBYE "; - out += toString(options["hash"]["cruel"]); + out += toString(options.lookup("hash.cruel")); out += " "; - out += toString(options["fn"](options["context"])); + out += toString(options.get("fn")(options.get("context"))); return out; } - else if (options["hash"]["print"] == false) + else if (options.lookup("hash.print") == false) { return "NOT PRINTING"; } @@ -4179,7 +4181,7 @@ helpers() std::string string = "{{hello}} {{link_to world}}"; hbs.registerHelper("helperMissing", []( dom::Value const& mesg, dom::Value const& options) { - if (options["name"] == "link_to") + if (options.get("name") == "link_to") { return safeString("" + mesg + ""); } @@ -4195,7 +4197,7 @@ helpers() { std::string string = "{{hello}} {{link_to}}"; hbs.registerHelper("helperMissing", [](dom::Value const& options) { - if (options["name"] == "link_to") + if (options.get("name") == "link_to") { return safeString("winning"); } @@ -4225,7 +4227,7 @@ helpers() std::string string = "{{#truthy}}yep{{/truthy}}"; dom::Object ctx; ctx.set("truthy", [](dom::Value const& options) { - return options["context"]["truthiness"](); + return options.lookup("context.truthiness")(); }); ctx.set("truthiness", []() { return false; @@ -4238,13 +4240,13 @@ helpers() { hbs = {}; hbs.registerHelper("blockHelperMissing", dom::makeVariadicInvocable([](dom::Array const& args) { - return "missing: " + args.back()["name"]; + return "missing: " + args.back().get("name"); })); hbs.registerHelper("helperMissing", dom::makeVariadicInvocable([](dom::Array const& args) { - return "helper missing: " + args.back()["name"]; + return "helper missing: " + args.back().get("name"); })); hbs.registerHelper("helper", dom::makeVariadicInvocable([](dom::Array const& args) { - return "ran: " + args.back()["name"]; + return "ran: " + args.back().get("name"); })); // should include in ambiguous mustache calls @@ -4307,7 +4309,7 @@ helpers() ctx.set("goodbye", "goodbye"); ctx.set("world", "world"); hbs.registerHelper("goodbye", [](dom::Value const& options) { - std::string res(options["context"]["goodbye"]); + std::string res(options.lookup("context.goodbye")); std::transform(res.begin(), res.end(), res.begin(), [](char c) { return static_cast(std::toupper(c)); }); @@ -4329,11 +4331,11 @@ helpers() ctx.set("goodbye", "goodbye"); ctx.set("world", "world"); hbs.registerHelper("goodbye", [](dom::Value const& options) { - std::string res(options["context"]["goodbye"]); + std::string res(options.lookup("context.goodbye")); std::transform(res.begin(), res.end(), res.begin(), [](char c) { return static_cast(std::toupper(c)); }); - return res + options["fn"](options["context"]); + return res + options.get("fn")(options.get("context")); }); hbs.registerHelper("cruel", [](dom::Value const& worldV) { std::string world(worldV); @@ -4351,7 +4353,7 @@ helpers() ctx.set("goodbye", "goodbye"); ctx.set("world", "world"); hbs.registerHelper("goodbye", [](dom::Value const& options) { - std::string res = toString(options["context"]["goodbye"]); + std::string res = toString(options.lookup("context.goodbye")); std::transform(res.begin(), res.end(), res.begin(), [](char c) { return static_cast(std::toupper(c)); }); @@ -4373,11 +4375,11 @@ helpers() ctx.set("goodbye", "goodbye"); ctx.set("world", "world"); hbs.registerHelper("goodbye", [](dom::Value const& options) { - std::string res = toString(options["context"]["goodbye"]); + std::string res = toString(options.lookup("context.goodbye")); std::transform(res.begin(), res.end(), res.begin(), [](char c) { return static_cast(std::toupper(c)); }); - return res + options["fn"](options["context"]); + return res + options.get("fn")(options.get("context")); }); hbs.registerHelper("cruel", [](dom::Value const& worldV) { std::string world(worldV); @@ -4397,14 +4399,14 @@ helpers() dom::Object ctx; ctx.set("value", "foo"); hbs.registerHelper("goodbyes", [](dom::Value const& options) { - BOOST_TEST(options["blockParams"] == 1); + BOOST_TEST(options.get("blockParams") == 1); dom::Object ctx; ctx.set("value", "bar"); dom::Array blockParams; blockParams.emplace_back(1); dom::Object fnOpt; fnOpt.set("blockParams", blockParams); - return options["fn"](ctx, fnOpt); + return options.get("fn")(ctx, fnOpt); }); BOOST_TEST(hbs.render("{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{value}}", ctx) == "1foo"); } @@ -4416,12 +4418,12 @@ helpers() return "foo"; }); hbs.registerHelper("goodbyes", [](dom::Value const& options) { - BOOST_TEST(options["blockParams"] == 1); + BOOST_TEST(options.get("blockParams") == 1); dom::Array blockParams; blockParams.emplace_back(1); dom::Object fnOpt; fnOpt.set("blockParams", blockParams); - return options["fn"](dom::Value{}, fnOpt); + return options.get("fn")(dom::Value{}, fnOpt); }); BOOST_TEST(hbs.render(string) == "1foo"); } @@ -4434,12 +4436,12 @@ helpers() return "foo"; }); hbs.registerHelper("goodbyes", [](dom::Value const& options) { - BOOST_TEST(options["blockParams"] == 1); + BOOST_TEST(options.get("blockParams") == 1); dom::Array blockParams; blockParams.emplace_back(1); dom::Object fnOpt; fnOpt.set("blockParams", blockParams); - return options["fn"](options["context"], fnOpt); + return options.get("fn")(options.get("context"), fnOpt); }); BOOST_TEST(hbs.render("{{#goodbyes as |value|}}{{./value}}{{/goodbyes}}{{value}}", ctx) == "barfoo"); } @@ -4453,7 +4455,7 @@ helpers() dom::Object ctx; ctx.set("value", "bar"); dom::Value blockParams; - if (options["blockParams"] == 1) + if (options.get("blockParams") == 1) { dom::Array a; a.emplace_back(value); @@ -4462,7 +4464,7 @@ helpers() } dom::Object fnOpt; fnOpt.set("blockParams", blockParams); - return options["fn"](ctx, fnOpt); + return options.get("fn")(ctx, fnOpt); }); std::string string = "{{#goodbyes as |value|}}{{#goodbyes}}{{value}}{{#goodbyes as |value|}}{{value}}{{/goodbyes}}{{/goodbyes}}{{/goodbyes}}{{value}}"; BOOST_TEST(hbs.render(string, ctx) == "13foo"); @@ -4473,14 +4475,14 @@ helpers() dom::Object ctx; ctx.set("value", "foo"); hbs.registerHelper("goodbyes", [](dom::Value const& options) { - BOOST_TEST(options["blockParams"] == 1); + BOOST_TEST(options.get("blockParams") == 1); dom::Object ctx; ctx.set("value", "bar"); dom::Array blockParams; blockParams.emplace_back(1); dom::Object fnOpt; fnOpt.set("blockParams", blockParams); - return options["fn"](ctx, fnOpt); + return options.get("fn")(ctx, fnOpt); }); BOOST_TEST(hbs.render("{{#if bar}}{{else goodbyes as |value|}}{{value}}{{/if}}{{value}}", ctx) == "1foo"); } @@ -4542,7 +4544,7 @@ helpers() // should be passed to custom helpers { hbs.registerHelper("testHelper", [](dom::Value const& options) { - return options["lookupProperty"](options["context"], "testProperty"); + return options.get("lookupProperty")(options.get("context"), "testProperty"); }); dom::Object ctx; ctx.set("testProperty", "abc"); @@ -4572,8 +4574,8 @@ track_ids() // should not include anything without the flag { hbs.registerHelper("wycats", [](dom::Value const& options) { - BOOST_TEST(options["ids"].empty()); - BOOST_TEST(options["hash"].empty()); + BOOST_TEST(options.get("ids").empty()); + BOOST_TEST(options.get("hash").empty()); return "success"; }); BOOST_TEST(hbs.render("{{wycats is.a slave.driver}}", context) == "success"); @@ -4583,14 +4585,14 @@ track_ids() { hbs.registerHelper("wycats", []( dom::Value const& passiveVoice, dom::Value const& noun, dom::Value const& options) { - BOOST_TEST(options["ids"][0] == "is.a"); - BOOST_TEST(options["ids"][1] == "slave.driver"); + BOOST_TEST(options.get("ids").get(0) == "is.a"); + BOOST_TEST(options.get("ids").get(1) == "slave.driver"); std::string res = "HELP ME MY BOSS "; - res += toString(options["ids"][0]); + res += toString(options.get("ids").get(0)); res += ":"; res += toString(passiveVoice); res += " "; - res += toString(options["ids"][1]); + res += toString(options.get("ids").get(1)); res += ":"; res += toString(noun); return res; @@ -4602,16 +4604,16 @@ track_ids() { std::string string = "{{wycats bat=is.a baz=slave.driver}}"; hbs.registerHelper("wycats", [](dom::Value const& options) { - BOOST_TEST(options["hashIds"]["bat"] == "is.a"); - BOOST_TEST(options["hashIds"]["baz"] == "slave.driver"); + BOOST_TEST(options.lookup("hashIds.bat") == "is.a"); + BOOST_TEST(options.lookup("hashIds.baz") == "slave.driver"); std::string res = "HELP ME MY BOSS "; - res += toString(options["hashIds"]["bat"]); + res += toString(options.lookup("hashIds.bat")); res += ":"; - res += toString(options["hash"]["bat"]); + res += toString(options.lookup("hash.bat")); res += " "; - res += toString(options["hashIds"]["baz"]); + res += toString(options.lookup("hashIds.baz")); res += ":"; - res += toString(options["hash"]["baz"]); + res += toString(options.lookup("hash.baz")); return res; }); BOOST_TEST(hbs.render(string, context, opt) == "HELP ME MY BOSS is.a:foo slave.driver:bar"); @@ -4626,16 +4628,16 @@ track_ids() dom::Value const& thiz, dom::Value const& thiz2, dom::Value const& options) { - BOOST_TEST(options["ids"][0] == "is.a"); - BOOST_TEST(options["ids"][1] == "../slave.driver"); - BOOST_TEST(options["ids"][2] == "is.a"); - BOOST_TEST(options["ids"][3].empty()); + BOOST_TEST(options.get("ids").get(0) == "is.a"); + BOOST_TEST(options.get("ids").get(1) == "../slave.driver"); + BOOST_TEST(options.get("ids").get(2) == "is.a"); + BOOST_TEST(options.get("ids").get(3).empty()); std::string res = "HELP ME MY BOSS "; - res += toString(options["ids"][0]); + res += toString(options.get("ids").get(0)); res += ":"; res += toString(passiveVoice); res += " "; - res += toString(options["ids"][1]); + res += toString(options.get("ids").get(1)); res += ":"; res += toString(noun); return res; @@ -4652,14 +4654,14 @@ track_ids() dom::Value const& noun, dom::Value const& options) { - BOOST_TEST(options["ids"][0].getString() == "@is.a"); - BOOST_TEST(options["ids"][1].getString() == "@slave.driver"); + BOOST_TEST(options.get("ids").get(0).getString() == "@is.a"); + BOOST_TEST(options.get("ids").get(1).getString() == "@slave.driver"); std::string res = "HELP ME MY BOSS "; - res += toString(options["ids"][0]); + res += toString(options.get("ids").get(0)); res += ":"; res += toString(passiveVoice); res += " "; - res += toString(options["ids"][1]); + res += toString(options.get("ids").get(1)); res += ":"; res += toString(noun); return res; @@ -4677,15 +4679,15 @@ track_ids() dom::Value const& noun, dom::Value const& options) { - BOOST_TEST(options["ids"][0].isNull()); - BOOST_TEST(options["ids"][1].isNull()); - BOOST_TEST(options["hashIds"]["key"].isNull()); + BOOST_TEST(options.get("ids").get(0).isNull()); + BOOST_TEST(options.get("ids").get(1).isNull()); + BOOST_TEST(options.lookup("hashIds.key").isNull()); std::string res = "HELP ME MY BOSS "; res += toString(passiveVoice); res += " "; res += toString(noun); res += " "; - res += toString(options["hash"]["key"]); + res += toString(options.lookup("hash.key")); return res; }); BOOST_TEST(hbs.render(string, context, opt) == "HELP ME MY BOSS 1 foo false"); @@ -4701,7 +4703,7 @@ track_ids() dom::Value const& passiveVoice, dom::Value const& options) { - BOOST_TEST(options["ids"][0] == true); + BOOST_TEST(options.get("ids").get(0) == true); return "HELP ME MY BOSS " + passiveVoice; }); BOOST_TEST(hbs.render(string, context, opt) == "HELP ME MY BOSS 1"); @@ -4712,13 +4714,13 @@ track_ids() std::string string = "{{#doIt as |is|}}{{wycats is.a slave.driver is}}{{/doIt}}"; hbs.registerHelper("doIt", [](dom::Value const& options) { dom::Array blockParams; - blockParams.emplace_back(options["context"]["is"]); + blockParams.emplace_back(options.lookup("context.is")); dom::Array blockParamPaths; blockParamPaths.emplace_back("zomg"); dom::Object fnOpt; fnOpt.set("blockParams", blockParams); fnOpt.set("blockParamPaths", blockParamPaths); - return options["fn"](options["context"], fnOpt); + return options.get("fn")(options.get("context"), fnOpt); }); hbs.registerHelper("wycats", []( dom::Value const& passiveVoice, @@ -4726,15 +4728,15 @@ track_ids() dom::Value const& blah, dom::Value const& options) { - BOOST_TEST(options["ids"][0] == "zomg.a"); - BOOST_TEST(options["ids"][1] == "slave.driver"); - BOOST_TEST(options["ids"][2] == "zomg"); + BOOST_TEST(options.get("ids").get(0) == "zomg.a"); + BOOST_TEST(options.get("ids").get(1) == "slave.driver"); + BOOST_TEST(options.get("ids").get(2) == "zomg"); std::string res = "HELP ME MY BOSS "; - res += toString(options["ids"][0]); + res += toString(options.get("ids").get(0)); res += ":"; res += toString(passiveVoice); res += " "; - res += toString(options["ids"][1]); + res += toString(options.get("ids").get(1)); res += ":"; res += toString(noun); return res; @@ -4745,11 +4747,11 @@ track_ids() hbs.registerHelper("blockParams", []( dom::Value const& name, dom::Value const& options) { - return name + ":" + options["ids"][0] + '\n'; + return name + ":" + options.get("ids").get(0) + '\n'; }); hbs.registerHelper("wycats", []( dom::Value const& name, dom::Value const& options) { - return name + ":" + options["data"]["contextPath"] + '\n'; + return name + ":" + options.lookup("data.contextPath") + '\n'; }); // builtin helpers @@ -5050,8 +5052,8 @@ strict() { std::string string = "{{helper value=@foo}}"; hbs.registerHelper("helper", [](dom::Value const& options) { - BOOST_TEST(options["hash"].exists("value")); - BOOST_TEST(options["hash"]["value"].isUndefined()); + BOOST_TEST(options.get("hash").exists("value")); + BOOST_TEST(options.lookup("hash.value").isUndefined()); return "success"; }); BOOST_TEST(hbs.render(string, dom::Object{}, opt) == "success"); diff --git a/test-files/handlebars/features_test.adoc b/test-files/handlebars/features_test.adoc index a6f6a40fd..135795838 100644 --- a/test-files/handlebars/features_test.adoc +++ b/test-files/handlebars/features_test.adoc @@ -52,25 +52,25 @@ People: [,json] ---- { - "page" : { - "kind" : "record", - "name" : "from_chars", - "decl" : "std::from_chars", - "loc" : "charconv", - "javadoc" : { - "brief" : "Converts strings to numbers", - "details" : "This function converts strings to numbers" + "page": { + "kind": "record", + "name": "from_chars", + "decl": "std::from_chars", + "loc": "charconv", + "javadoc": { + "brief": "Converts strings to numbers", + "details": "This function converts strings to numbers" }, - "synopsis" : "This is the from_chars function", - "person" : { - "firstname" : "John", - "lastname" : "Doe" + "synopsis": "This is the from_chars function", + "person": { + "firstname": "John", + "lastname": "Doe" }, - "people" : [ + "people": [ { - "firstname" : "Alice", - "lastname" : "Doe", - "book" : [ + "firstname": "Alice", + "lastname": "Doe", + "book": [ {}, {}, {}, @@ -78,9 +78,9 @@ People: ] }, { - "firstname" : "Bob", - "lastname" : "Doe", - "book" : [ + "firstname": "Bob", + "lastname": "Doe", + "book": [ {}, {}, {}, @@ -88,9 +88,9 @@ People: ] }, { - "firstname" : "Carol", - "lastname" : "Smith", - "book" : [ + "firstname": "Carol", + "lastname": "Smith", + "book": [ {}, {}, {}, @@ -98,118 +98,118 @@ People: ] } ], - "prefix" : "Hello", - "specialChars" : "& < > \" ' ` =", - "url" : "https://cppalliance.org/", - "author" : { - "firstname" : "Yehuda", - "lastname" : "Katz" + "prefix": "Hello", + "specialChars": "& < > \" ' ` =", + "url": "https://cppalliance.org/", + "author": { + "firstname": "Yehuda", + "lastname": "Katz" } }, - "nav" : [ + "nav": [ { - "url" : "foo", - "test" : true, - "title" : "bar" + "url": "foo", + "test": true, + "title": "bar" }, { - "url" : "bar" + "url": "bar" } ], - "myVariable" : "lookupMyPartial", - "myOtherContext" : { - "information" : "Interesting!" + "myVariable": "lookupMyPartial", + "myOtherContext": { + "information": "Interesting!" }, - "favoriteNumber" : 123, - "prefix" : "Hello", - "title" : "My Title", - "body" : "My Body", - "story" : { - "intro" : "Before the jump", - "body" : "After the jump" + "favoriteNumber": 123, + "prefix": "Hello", + "title": "My Title", + "body": "My Body", + "story": { + "intro": "Before the jump", + "body": "After the jump" }, - "comments" : [ + "comments": [ { - "subject" : "subject 1", - "body" : "body 1" + "subject": "subject 1", + "body": "body 1" }, { - "subject" : "subject 2", - "body" : "body 2" + "subject": "subject 2", + "body": "body 2" } ], - "isActive" : true, - "isInactive" : false, - "peopleobj" : { - "Alice" : { - "firstname" : "Alice", - "lastname" : "Doe" + "isActive": true, + "isInactive": false, + "peopleobj": { + "Alice": { + "firstname": "Alice", + "lastname": "Doe" }, - "Bob" : { - "firstname" : "Bob", - "lastname" : "Doe" + "Bob": { + "firstname": "Bob", + "lastname": "Doe" }, - "Carol" : { - "firstname" : "Carol", - "lastname" : "Smith" + "Carol": { + "firstname": "Carol", + "lastname": "Smith" } }, - "author" : true, - "firstname" : "Yehuda", - "lastname" : "Katz", - "names" : [ + "author": true, + "firstname": "Yehuda", + "lastname": "Katz", + "names": [ "Yehuda Katz", "Alan Johnson", "Charles Jolley" ], - "namesobj" : { - "Yehuda" : "Yehuda Katz", - "Alan" : "Alan Johnson", - "Charles" : "Charles Jolley" + "namesobj": { + "Yehuda": "Yehuda Katz", + "Alan": "Alan Johnson", + "Charles": "Charles Jolley" }, - "city" : { - "name" : "San Francisco", - "summary" : "San Francisco is the cultural center of Northern California", - "location" : { - "north" : "37.73,", - "east" : "-122.44" + "city": { + "name": "San Francisco", + "summary": "San Francisco is the cultural center of Northern California", + "location": { + "north": "37.73,", + "east": "-122.44" }, - "population" : 883305 + "population": 883305 }, - "lookup_test" : { - "people" : [ + "lookup_test": { + "people": [ "Nils", "Yehuda" ], - "cities" : [ + "cities": [ "Darmstadt", "San Francisco" ] }, - "lookup_test2" : { - "persons" : [ + "lookup_test2": { + "persons": [ { - "name" : "Nils", - "resident-in" : "darmstadt" + "name": "Nils", + "resident-in": "darmstadt" }, { - "name" : "Yehuda", - "resident-in" : "san-francisco" + "name": "Yehuda", + "resident-in": "san-francisco" } ], - "cities" : { - "darmstadt" : { - "name" : "Darmstadt", - "country" : "Germany" + "cities": { + "darmstadt": { + "name": "Darmstadt", + "country": "Germany" }, - "san-francisco" : { - "name" : "San Francisco", - "country" : "USA" + "san-francisco": { + "name": "San Francisco", + "country": "USA" } } }, - "containers" : { - "array" : [ + "containers": { + "array": [ "a", "b", "c", @@ -218,7 +218,7 @@ People: "f", "g" ], - "array2" : [ + "array2": [ "e", "f", "g", @@ -227,43 +227,43 @@ People: "j", "k" ], - "object" : { - "a" : "a", - "b" : "b", - "c" : "c", - "d" : "d", - "e" : "e", - "f" : "f", - "g" : "g" + "object": { + "a": "a", + "b": "b", + "c": "c", + "d": "d", + "e": "e", + "f": "f", + "g": "g" }, - "object2" : { - "e" : "e", - "f" : "f", - "g" : "g", - "h" : "h", - "i" : "i", - "j" : "j", - "k" : "k" + "object2": { + "e": "e", + "f": "f", + "g": "g", + "h": "h", + "i": "i", + "j": "j", + "k": "k" }, - "object_array" : [ + "object_array": [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] }, - "symbol" : { - "tag" : "struct", - "kind" : "record", - "name" : "T" + "symbol": { + "tag": "struct", + "kind": "record", + "name": "T" } } ---- @@ -834,64 +834,64 @@ list helper requires array argument: object provided [a,b,c,d,e,f,g] [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] // del [a,b,d,e,f,g] { - "a" : "a", - "b" : "b", - "d" : "d", - "e" : "e", - "f" : "f", - "g" : "g" + "a": "a", + "b": "b", + "d": "d", + "e": "e", + "f": "f", + "g": "g" } [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] // delete [a,b,d,e,f,g] { - "a" : "a", - "b" : "b", - "d" : "d", - "e" : "e", - "f" : "f", - "g" : "g" + "a": "a", + "b": "b", + "d": "d", + "e": "e", + "f": "f", + "g": "g" } [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] // has @@ -930,8 +930,8 @@ false c c { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } // get_or y @@ -942,16 +942,16 @@ y [[a,a],[b,b],[c,c],[d,d],[e,e],[f,f],[g,g]] [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] // entries @@ -959,59 +959,59 @@ y [[a,a],[b,b],[c,c],[d,d],[e,e],[f,f],[g,g]] [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] // first a "a" { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" } // head a "a" { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" } // front a "a" { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" } // last g "g" { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } // tail g "g" { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } // back g "g" { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } // reverse [g,f,e,d,c,b,a] @@ -1047,16 +1047,16 @@ g ] [ { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" } ] // reversed @@ -1093,45 +1093,45 @@ g ] [ { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" } ] // update [a,b,c,d,e,f,g,h,i,j,k] { - "e" : "e", - "f" : "f", - "g" : "g", - "h" : "h", - "i" : "i", - "j" : "j", - "k" : "k", - "a" : "a", - "b" : "b", - "c" : "c", - "d" : "d" + "e": "e", + "f": "f", + "g": "g", + "h": "h", + "i": "i", + "j": "j", + "k": "k", + "a": "a", + "b": "b", + "c": "c", + "d": "d" } [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" }, "e", "f", @@ -1144,30 +1144,30 @@ g // merge [a,b,c,d,e,f,g,h,i,j,k] { - "e" : "e", - "f" : "f", - "g" : "g", - "h" : "h", - "i" : "i", - "j" : "j", - "k" : "k", - "a" : "a", - "b" : "b", - "c" : "c", - "d" : "d" + "e": "e", + "f": "f", + "g": "g", + "h": "h", + "i": "i", + "j": "j", + "k": "k", + "a": "a", + "b": "b", + "c": "c", + "d": "d" } [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" }, "e", "f", @@ -1180,35 +1180,35 @@ g // sort [a,b,c,d,e,f,g] { - "a" : "a", - "b" : "b", - "c" : "c", - "d" : "d", - "e" : "e", - "f" : "f", - "g" : "g" + "a": "a", + "b": "b", + "c": "c", + "d": "d", + "e": "e", + "f": "f", + "g": "g" } // sort_by [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] // at c c { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } // fill [a,b,-,-,-,f,g] @@ -1225,99 +1225,99 @@ c [a,b,d,d,e,f,g] [a,b,d,d,e,f,g] { - "c" : "d", - "a" : "a", - "b" : "b", - "d" : "d", - "e" : "e", - "f" : "f", - "g" : "g" + "c": "d", + "a": "a", + "b": "b", + "d": "d", + "e": "e", + "f": "f", + "g": "g" } [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" }, { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] // chunk [[a,b,c],[d,e,f],[g]] [ { - "a" : "a", - "b" : "b", - "c" : "c" + "a": "a", + "b": "b", + "c": "c" }, { - "d" : "d", - "e" : "e", - "f" : "f" + "d": "d", + "e": "e", + "f": "f" }, { - "g" : "g" + "g": "g" } ] [ [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" } ], [ { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] ] // group_by { - "account-x10" : [ + "account-x10": [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" }, { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" } ], - "account-x11" : [ + "account-x11": [ { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] } { - "Chair" : [ + "Chair": [ { - "account_id" : "account-x10", - "product" : "Chair" + "account_id": "account-x10", + "product": "Chair" } ], - "Bookcase" : [ + "Bookcase": [ { - "account_id" : "account-x10", - "product" : "Bookcase" + "account_id": "account-x10", + "product": "Bookcase" } ], - "Desk" : [ + "Desk": [ { - "account_id" : "account-x11", - "product" : "Desk" + "account_id": "account-x11", + "product": "Desk" } ] }