From 9b4ad8b81fcdf3e6574b225589fb812b6b6c30a9 Mon Sep 17 00:00:00 2001 From: John French Date: Fri, 12 Mar 2021 22:18:01 +0530 Subject: [PATCH] src: add iterator for Object Refs: https://github.com/nodejs/node-addon-api/issues/830 Signed-off-by: Darshan Sen --- doc/object.md | 42 +++++++++++++++++++++ napi-inl.h | 85 +++++++++++++++++++++++++++++++++++++++++++ napi.h | 72 ++++++++++++++++++++++++++++++++++++ test/object/object.cc | 28 ++++++++++++++ test/object/object.js | 58 +++++++++++++++++++++++++++++ 5 files changed, 285 insertions(+) diff --git a/doc/object.md b/doc/object.md index 9830675..8aa872d 100644 --- a/doc/object.md +++ b/doc/object.md @@ -288,5 +288,47 @@ Napi::Value Napi::Object::operator[] (uint32_t index) const; Returns an indexed property or array element as a [`Napi::Value`](value.md). +## Iterator + +Iterators expose an `std::pair<...>`, where the `first` property is a +[`Napi::Value`](value.md) that holds the currently iterated key and the +`second` property is a [`Napi::Object::PropertyLValue`](propertylvalue.md) that +holds the currently iterated value. Iterators are only available if C++ +exceptions are enabled (by defining `NAPI_CPP_EXCEPTIONS` during the build). + +### Constant Iterator + +In constant iterators, the iterated values are immutable. + +```cpp +Value Sum(const CallbackInfo& info) { + Object object = info[0].As(); + int64_t sum = 0; + + for (const auto& e : object) { + sum += static_cast(e.second).As().Int64Value(); + } + + return Number::New(info.Env(), sum); +} +``` + +### Non Constant Iterator + +In non constant iterators, the iterated values are mutable. + +```cpp +void Increment(const CallbackInfo& info) { + Env env = info.Env(); + Object object = info[0].As(); + + for (auto e : object) { + int64_t value = static_cast(e.second).As().Int64Value(); + ++value; + e.second = Napi::Number::New(env, value); + } +} +``` + [`Napi::Value`]: ./value.md [`Napi::Value::From`]: ./value.md#from diff --git a/napi-inl.h b/napi-inl.h index 5e51cc3..19aaab4 100644 --- a/napi-inl.h +++ b/napi-inl.h @@ -1306,6 +1306,14 @@ inline Object::PropertyLValue Object::operator [](uint32_t index) { return PropertyLValue(*this, index); } +inline Object::PropertyLValue Object::operator[](Value index) { + return PropertyLValue(*this, index); +} + +inline Object::PropertyLValue Object::operator[](Value index) const { + return PropertyLValue(*this, index); +} + inline MaybeOrValue Object::operator[](const char* utf8name) const { return Get(utf8name); } @@ -1530,6 +1538,83 @@ inline void Object::AddFinalizer(Finalizer finalizeCallback, } } +#ifdef NAPI_CPP_EXCEPTIONS +inline Object::const_iterator::const_iterator(const Object* object, + const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::const_iterator Napi::Object::begin() const { + const_iterator it(this, Object::const_iterator::Type::BEGIN); + return it; +} + +inline Object::const_iterator Napi::Object::end() const { + const_iterator it(this, Object::const_iterator::Type::END); + return it; +} + +inline Object::const_iterator& Object::const_iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::const_iterator::operator==( + const const_iterator& other) const { + return _index == other._index; +} + +inline bool Object::const_iterator::operator!=( + const const_iterator& other) const { + return _index != other._index; +} + +inline const std::pair> +Object::const_iterator::operator*() const { + const Value key = _keys[_index]; + const PropertyLValue value = (*_object)[key]; + return {key, value}; +} + +inline Object::iterator::iterator(Object* object, const Type type) { + _object = object; + _keys = object->GetPropertyNames(); + _index = type == Type::BEGIN ? 0 : _keys.Length(); +} + +inline Object::iterator Napi::Object::begin() { + iterator it(this, Object::iterator::Type::BEGIN); + return it; +} + +inline Object::iterator Napi::Object::end() { + iterator it(this, Object::iterator::Type::END); + return it; +} + +inline Object::iterator& Object::iterator::operator++() { + ++_index; + return *this; +} + +inline bool Object::iterator::operator==(const iterator& other) const { + return _index == other._index; +} + +inline bool Object::iterator::operator!=(const iterator& other) const { + return _index != other._index; +} + +inline std::pair> +Object::iterator::operator*() { + Value key = _keys[_index]; + PropertyLValue value = (*_object)[key]; + return {key, value}; +} +#endif // NAPI_CPP_EXCEPTIONS + #if NAPI_VERSION >= 8 inline MaybeOrValue Object::Freeze() { napi_status status = napi_object_freeze(_env, _value); diff --git a/napi.h b/napi.h index 8475bfc..e1ddd00 100644 --- a/napi.h +++ b/napi.h @@ -741,6 +741,14 @@ namespace Napi { uint32_t index /// Property / element index ); + /// Gets or sets an indexed property or array element. + PropertyLValue operator[](Value index /// Property / element index + ); + + /// Gets or sets an indexed property or array element. + PropertyLValue operator[](Value index /// Property / element index + ) const; + /// Gets a named property. MaybeOrValue operator[]( const char* utf8name ///< UTF-8 encoded null-terminated property name @@ -928,6 +936,21 @@ namespace Napi { inline void AddFinalizer(Finalizer finalizeCallback, T* data, Hint* finalizeHint); + +#ifdef NAPI_CPP_EXCEPTIONS + class const_iterator; + + inline const_iterator begin() const; + + inline const_iterator end() const; + + class iterator; + + inline iterator begin(); + + inline iterator end(); +#endif // NAPI_CPP_EXCEPTIONS + #if NAPI_VERSION >= 8 /// This operation can fail in case of Proxy.[[GetPrototypeOf]] calling into /// JavaScript. @@ -976,6 +999,55 @@ namespace Napi { uint32_t Length() const; }; +#ifdef NAPI_CPP_EXCEPTIONS + class Object::const_iterator { + private: + enum class Type { BEGIN, END }; + + inline const_iterator(const Object* object, const Type type); + + public: + inline const_iterator& operator++(); + + inline bool operator==(const const_iterator& other) const; + + inline bool operator!=(const const_iterator& other) const; + + inline const std::pair> operator*() + const; + + private: + const Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; + }; + + class Object::iterator { + private: + enum class Type { BEGIN, END }; + + inline iterator(Object* object, const Type type); + + public: + inline iterator& operator++(); + + inline bool operator==(const iterator& other) const; + + inline bool operator!=(const iterator& other) const; + + inline std::pair> operator*(); + + private: + Napi::Object* _object; + Array _keys; + uint32_t _index; + + friend class Object; + }; +#endif // NAPI_CPP_EXCEPTIONS + /// A JavaScript array buffer value. class ArrayBuffer : public Object { public: diff --git a/test/object/object.cc b/test/object/object.cc index a73b8e3..6227b23 100644 --- a/test/object/object.cc +++ b/test/object/object.cc @@ -253,6 +253,30 @@ Value CreateObjectUsingMagic(const CallbackInfo& info) { return obj; } +#ifdef NAPI_CPP_EXCEPTIONS +Value Sum(const CallbackInfo& info) { + Object object = info[0].As(); + int64_t sum = 0; + + for (const auto& e : object) { + sum += static_cast(e.second).As().Int64Value(); + } + + return Number::New(info.Env(), sum); +} + +void Increment(const CallbackInfo& info) { + Env env = info.Env(); + Object object = info[0].As(); + + for (auto e : object) { + int64_t value = static_cast(e.second).As().Int64Value(); + ++value; + e.second = Napi::Number::New(env, value); + } +} +#endif // NAPI_CPP_EXCEPTIONS + Value InstanceOf(const CallbackInfo& info) { Object obj = info[0].As(); Function constructor = info[1].As(); @@ -299,6 +323,10 @@ Object InitObject(Env env) { exports["hasPropertyWithCppStyleString"] = Function::New(env, HasPropertyWithCppStyleString); exports["createObjectUsingMagic"] = Function::New(env, CreateObjectUsingMagic); +#ifdef NAPI_CPP_EXCEPTIONS + exports["sum"] = Function::New(env, Sum); + exports["increment"] = Function::New(env, Increment); +#endif // NAPI_CPP_EXCEPTIONS exports["addFinalizer"] = Function::New(env, AddFinalizer); exports["addFinalizerWithHint"] = Function::New(env, AddFinalizerWithHint); diff --git a/test/object/object.js b/test/object/object.js index ef06ad2..13e31ce 100644 --- a/test/object/object.js +++ b/test/object/object.js @@ -156,4 +156,62 @@ function test(binding) { assert.strictEqual(binding.object.instanceOf({}, Ctor), false); assert.strictEqual(binding.object.instanceOf(null, Ctor), false); } + + if ('sum' in binding.object) { + { + const obj = { + '-forbid': -0x4B1D, + '-feedcode': -0xFEEDC0DE, + '+office': +0x0FF1CE, + '+forbid': +0x4B1D, + '+deadbeef': +0xDEADBEEF, + '+feedcode': +0xFEEDC0DE, + }; + + let sum = 0; + for (const key in obj) { + sum += obj[key]; + } + + assert.strictEqual(binding.object.sum(obj), sum); + } + + { + const obj = new Proxy({ + '-forbid': -0x4B1D, + '-feedcode': -0xFEEDC0DE, + '+office': +0x0FF1CE, + '+forbid': +0x4B1D, + '+deadbeef': +0xDEADBEEF, + '+feedcode': +0xFEEDC0DE, + }, { + getOwnPropertyDescriptor(target, p) { + throw new Error("getOwnPropertyDescriptor error"); + }, + ownKeys(target) { + throw new Error("ownKeys error"); + }, + }); + + assert.throws(() => { + binding.object.sum(obj); + }, /ownKeys error/); + } + } + + if ('increment' in binding.object) { + const obj = { + 'a': 0, + 'b': 1, + 'c': 2, + }; + + binding.object.increment(obj); + + assert.deepStrictEqual(obj, { + 'a': 1, + 'b': 2, + 'c': 3, + }); + } }